diff --git a/package.json b/package.json
index 3c1432e3d999b18a1c66fc6debd265aead2aab51..53b1aa4694828dac6109ab474147565a58243d54 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
     "nunjucks": "^2.3.0",
     "nunjucks-date-filter": "^0.1.1",
     "sqlite-parser": "^0.14.3",
+    "ssh2": "^0.5.0",
     "syntax-error": "^1.1.5",
     "toml": "^2.3.0",
     "toml-js": "0.0.8",
diff --git a/sftp-sync.js b/sftp-sync.js
new file mode 100644
index 0000000000000000000000000000000000000000..69771a130059af08bef64a4d61edf0a2d997ba6a
--- /dev/null
+++ b/sftp-sync.js
@@ -0,0 +1,302 @@
+var crypto = require('crypto');
+var walk = require('walk');
+var fs = require('fs');
+var path = require('path');
+var cli = require('cli');
+
+var options = cli.parse({
+ sourcedir: [null, 'From where to upload (required)', 'string'],
+ remotedir: [null, 'Target directory for upload (required)', 'string'],
+ server: ['s', 'SFTP server (required)', 'string'],
+ user: ['u', 'SFTP user (required)', 'string'],
+ port: ['p', 'port', 'number', 22]
+});
+
+function missingArguments() { console.log('Missing required arguments. Use --help.')}
+if(!options.sourcedir) return missingArguments();
+if(!options.server) return missingArguments();
+if(!options.user) return missingArguments();
+if(!options.remotedir) return missingArguments();
+if(!process.env.PASSWORD) return console.log('Missing environment variable PASSWORD');
+
+var Client = require('ssh2').Client;
+
+var conn = new Client();
+function connect(arg) {
+  return new Promise(function(resolve, reject) {
+    var connReady = false;
+
+    var resolved = false;
+    conn.once('ready', function() {
+      console.log('Client :: ready');
+      if(!resolved) {
+        resolved = true;
+        resolve(arg);
+      }
+    })
+    .once('error', function() {
+      if(resolved) {
+        console.log(new Error('Failed to connect to server').stack);
+        process.exit(0);
+      } else {
+        resolved = true;
+        reject(new Error('Failed to connect to server'));
+      }
+    })
+    .connect({
+      host: options.server,
+      port: options.port,
+      username: options.user,
+      password: process.env.PASSWORD
+    });
+  })
+}
+
+function checksum (str, algorithm, encoding) {
+    return crypto
+        .createHash(algorithm || 'sha256')
+        .update(str, 'utf8')
+        .digest(encoding || 'hex')
+}
+
+function fileChecksum(file) {
+  return new Promise(function(resolve, reject) {
+    fs.readFile(file, function (err, data) {
+      if(err) reject(new Error('Error reading file '+ file+' '+err));
+      resolve({ file: file, checksum: checksum(data)});
+    });
+  })
+}
+
+new Promise(function(resolve, reject) {
+  console.log('Upload step: Listing files');
+  var files = [];
+  walk.walk(options.sourcedir)
+  .on('file',function(root,fileStats,next) {
+    var filename = path.join(root,fileStats.name);
+    files.push(filename);
+    next();
+  })
+  .on('errors',function(root, nodeStatsArray, next) {
+    console.log('Walker error', root, nodeStatsArray);
+    next();
+  })
+  .on('end', function() {
+    resolve(files);
+  })
+})
+.then(function(files) {
+  console.log('Upload step: Generating checksums');
+  var promises = [];
+  files.forEach(function(file) {
+    promises.push(fileChecksum(file))
+  })
+  return Promise.all(promises);
+})
+.then(function(localchecksums) {
+  console.log('Upload step: Making paths relative')
+  var r = [];
+  localchecksums.forEach(function(cs) {
+    cs.file = path.relative(options.sourcedir, cs.file);
+    r.push(cs);
+  })
+  return r;
+})
+.then(connect)
+.then(function(localchecksums) {
+  console.log('Upload step: Getting remote checksums');
+  return new Promise(function(resolve, reject) {
+    conn.sftp(function(err, sftp) {
+      if (err) reject(err);
+      var data = '';
+      sftp.createReadStream(options.remotedir+'/checksums.json', 'utf-8')
+      .on('data', function(chunk) {
+        data+=chunk;
+      })
+      .on('end', function() {
+        resolve({local: localchecksums, remote: JSON.parse(data), sftp: sftp});
+      })
+      .on('error', function(e) {
+        resolve({local: localchecksums, remote: [], sftp: sftp});
+      })
+    });
+  })
+})
+/*
+.then(function(obj) {
+  console.log('Upload step: Listing remote directory');
+
+  return new Promise(function(resolve, reject) {
+    var files = [];
+    var readDir = function(dir) {
+      console.log('Reading dir', dir);
+      return new Promise(function(resolve, reject) {
+        obj.sftp.readdir(dir, function(err, list) {
+          if (err) reject(err);
+          var promises = [];
+          list.forEach(function(entry) {
+            if(entry.longname.substring(0,1) == 'd') { // directory
+              promises.push(readDir(dir+'/'+entry.filename));
+            } else {
+              promises.push(Promise.resolve(dir+'/'+entry.filename));
+            }
+          })
+          resolve(Promise.all(promises));
+        });
+      });
+    }
+    var flatten = function(array) {
+      return [].concat.apply([], array);
+    }
+    readDir(options.remotedir)
+    .then(flatten)
+    .then(function(list) {
+      obj.remoteFiles = list;
+      resolve(obj);
+    })
+  });
+})
+*/
+.then(function(obj) {
+  console.log('Upload step: Deleting old files');
+
+  var localAssoc = {};
+  obj.local.forEach(function(entry) {
+    localAssoc[entry.file] = entry.checksum;
+  })
+
+  var promises = [];
+
+  obj.remote.forEach(function(entry) {
+    if(localAssoc[entry.file] != entry.checksum && entry.file != 'checksums.json') {
+      console.log('    deleting',entry.file);
+      promises.push(new Promise(function(resolve, reject) {
+        obj.sftp.unlink(options.remotedir+'/'+entry.file, function(err) {
+          if(err) console.log('    [Warning] Error deleting '+entry.file);
+          resolve();
+        })
+      }));
+    }
+  })
+
+  return Promise.all(promises)
+  .then(function(){
+    return obj;
+  })
+})
+.then(function(obj) {
+  console.log('Upload step: Creating required directories');
+
+  var remoteAssoc = {};
+  obj.remote.forEach(function(entry) {
+    remoteAssoc[entry.file] = entry.checksum;
+  })
+
+  var toUpload = [];
+  obj.local.forEach(function(entry) {
+    if(remoteAssoc[entry.file] != entry.checksum) {
+      var dir = path.dirname(entry.file);
+      if(dir !== '.') toUpload.push(dir);
+    }
+  })
+  function uniq(a) {
+    var seen = {};
+    return a.filter(function(item) {
+        return seen.hasOwnProperty(item) ? false : (seen[item] = true);
+    });
+  }
+  toUpload = uniq(toUpload);
+  var pushParents = function(dir) {
+    var parent = path.dirname(dir);
+    if(parent == '.') return;
+    toUpload.push(parent);
+    pushParents(parent);
+  }
+  toUpload.forEach(function(file) {
+    pushParents(file);
+  })
+  toUpload = uniq(toUpload.reverse());
+
+  var promises = [];
+  toUpload.forEach(function(dir) {
+    promises.push(new Promise(function(resolve, reject) {
+      console.log('Creating directory '+dir);
+      obj.sftp.mkdir(options.remotedir+'/'+dir,function() {
+        resolve();
+      })
+    }))
+  })
+  return Promise.all(promises)
+  .then(function() {
+    return obj;
+  })
+})
+.then(function(obj) {
+  console.log('Upload step: Uploading new files');
+
+  var remoteAssoc = {};
+  obj.remote.forEach(function(entry) {
+    remoteAssoc[entry.file] = entry.checksum;
+  })
+
+  var promises = [];
+
+  obj.local.forEach(function(entry) {
+    if(remoteAssoc[entry.file] != entry.checksum) {
+      console.log('    Uploading',entry.file);
+      promises.push(new Promise(function(resolve, reject) {
+        obj.sftp.fastPut(options.sourcedir+'/'+entry.file,options.remotedir+'/'+entry.file, function(err) {
+          if(err)console.log('    [Warning] Error uploading '+entry.file);
+          else console.log('    Uploaded',entry.file);
+          resolve();
+        })
+      }));
+    }
+  })
+
+  return Promise.all(promises)
+  .then(function(){
+    return obj;
+  })
+})
+.then(function(obj) {
+  console.log('Upload step: Uploading checksums.json');
+  return new Promise(function(resolve, reject) {
+    console.log('    Creating checksums.json');
+    fs.writeFile(options.sourcedir+'/checksums.json', JSON.stringify(obj.local, null, '  '), 'utf-8', function() {
+      resolve();
+    });
+  })
+  .then(function() {
+    console.log('    Removing remote checksums.json');
+    return new Promise(function(resolve, reject) {
+      obj.sftp.unlink(options.remotedir+'/checksums.json', function(err) {
+        resolve();
+      })
+    })
+  })
+  .then(function() {
+    console.log('    Uploading checksums.json');
+    return new Promise(function(resolve, reject){
+      obj.sftp.fastPut(options.sourcedir+'/checksums.json',options.remotedir+'/checksums.json', function(err) {
+        if(err)console.log('    [Warning] Error uploading checksums.json');
+        resolve();
+      })
+    });
+  })
+  .then(function() {
+    console.log('    Removing local checksums.json');
+    return new Promise(function(resolve, reject){
+      fs.unlink(options.sourcedir+'/checksums.json', function() {
+        resolve();
+      })
+    })
+  })
+})
+.then(function() {
+  conn.end();
+})
+.catch(function(e) {
+  conn.end();
+  console.log(e.stack);
+})
diff --git a/upload b/upload
index ed8bc877120a82207da4338ad45d9d1884c010cc..3a7c012f6fccdd5105ed898426b6f4cbbefac5fd 100755
--- a/upload
+++ b/upload
@@ -4,6 +4,7 @@ then
 	echo -n "Heslo pro upload: "
 	read -s LFTP_PASSWORD; export LFTP_PASSWORD
 fi
+export PASSWORD=$LFTP_PASSWORD
 
 USER=ok1kvk.cz-www-nove
 HOST=krios.blueboard.cz
@@ -26,16 +27,7 @@ if [ "$1" == "ftp" ]; then
     echo "LFTP finished with return code $RET"
 else
     echo "Using SFTP"
-    time lftp -e "set sftp:auto-confirm yes;\
-                  set cmd:fail-exit yes;\
-                  set net:timeout 5;\
-                  set net:reconnect-interval-base $RECONNECT_INTERVAL;\
-                  set net:max-retries $MAX_RETRIES;\
-                  open --user $USER --env-password -p 2121 sftp://$HOST/;\
-                  mirror -c --verbose=9 -e -R -L ./build /;\
-                  exit 0;"
-    RET=$?
-    echo "LFTP finished with return code $RET"
+    /usr/bin/time -f "Upload took %e" -- node sftp-sync.js --sourcedir build --server $HOST --user $USER --remotedir /test --port 2121
 fi
 exit 0