diff --git a/Cargo.lock b/Cargo.lock
index 4bf2540bdb158bbca43e357124991b1605b8152a..d2d1facf2b5bdcc0ebb96a68fe27735f90c5a3e9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -646,6 +646,7 @@ dependencies = [
  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "qr2term 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1251,6 +1252,11 @@ dependencies = [
  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "pathdiff"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "pbr"
 version = "1.0.1"
@@ -2433,6 +2439,7 @@ dependencies = [
 "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
 "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
 "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
+"checksum pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3bf70094d203e07844da868b634207e71bfab254fe713171fae9a6e751ccf31"
 "checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
 "checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
diff --git a/Cargo.toml b/Cargo.toml
index 2e973a3a1984c03d59946c17341f1b389304717b..db3d89cb11b9bfaf662090fd3a294f3a2222cbe7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -108,6 +108,7 @@ fs2 = "0.4"
 lazy_static = "1.0"
 open = "1"
 openssl-probe = "0.1"
+pathdiff = "0.1"
 pbr = "1"
 prettytable-rs = "0.8"
 qr2term = { version = "0.1", optional = true }
diff --git a/src/action/upload.rs b/src/action/upload.rs
index 6837b603d43e5d612bea62dc82f271f4832fa590..ecd34cdc6cae69cfbc26a008a98dc4ff65c13ace 100644
--- a/src/action/upload.rs
+++ b/src/action/upload.rs
@@ -1,6 +1,11 @@
+use std::env::current_dir;
 #[cfg(feature = "archive")]
 use std::io::Error as IoError;
 use std::path::Path;
+#[cfg(feature = "archive")]
+use std::path::PathBuf;
+#[cfg(feature = "archive")]
+use std::process::exit;
 use std::sync::{Arc, Mutex};
 
 use clap::ArgMatches;
@@ -10,6 +15,7 @@ use ffsend_api::action::upload::{Error as UploadError, Upload as ApiUpload};
 use ffsend_api::action::version::Error as VersionError;
 use ffsend_api::config::{upload_size_max, UPLOAD_SIZE_MAX_RECOMMENDED};
 use ffsend_api::pipe::ProgressReporter;
+use pathdiff::diff_paths;
 use prettytable::{format::FormatBuilder, Cell, Row, Table};
 #[cfg(feature = "qrcode")]
 use qr2term::print_qr;
@@ -53,9 +59,183 @@ impl<'a> Upload<'a> {
 
         // Get API parameters
         #[allow(unused_mut)]
-        let mut path = Path::new(matcher_upload.file()).to_path_buf();
+        let mut paths: Vec<_> = matcher_upload
+            .files()
+            .into_iter()
+            .map(|p| Path::new(p).to_path_buf())
+            .collect();
+        let mut path = Path::new(paths.first().unwrap()).to_path_buf();
         let host = matcher_upload.host();
 
+        // The file name to use
+        #[allow(unused_mut)]
+        let mut file_name = matcher_upload.name().map(|s| s.to_owned());
+
+        // All paths must exist
+        // TODO: ensure the file exists and is accessible
+        for path in &paths {
+            if !path.exists() {
+                quit_error_msg(
+                    format!("the path '{}' does not exist", path.to_str().unwrap_or("?")),
+                    ErrorHintsBuilder::default().build().unwrap(),
+                );
+            }
+        }
+
+        // A temporary archive file, only used when archiving
+        // The temporary file is stored here, to ensure it's lifetime exceeds the upload process
+        #[allow(unused_mut)]
+        #[cfg(feature = "archive")]
+        let mut tmp_archive: Option<NamedTempFile> = None;
+
+        #[cfg(feature = "archive")]
+        {
+            // Determine whether to archive, we must archive for multiple files/directory
+            let mut archive = matcher_upload.archive();
+            if !archive {
+                if paths.len() > 1 {
+                    if prompt_yes(
+                        "You've selected multiple files, only a single file may be uploaded.\n\
+                         Archive the files into a single file?",
+                        Some(true),
+                        &matcher_main,
+                    ) {
+                        archive = true;
+                    } else {
+                        exit(1);
+                    }
+                } else if path.is_dir() {
+                    if prompt_yes(
+                        "You've selected a directory, only a single file may be uploaded.\n\
+                         Archive the directory into a single file?",
+                        Some(true),
+                        &matcher_main,
+                    ) {
+                        archive = true;
+                    } else {
+                        exit(1);
+                    }
+                }
+            }
+
+            // Archive the selected file or directory
+            if archive {
+                eprintln!("Archiving...");
+                let archive_extention = ".tar";
+
+                // Create a new temporary file to write the archive to
+                tmp_archive = Some(
+                    TempBuilder::new()
+                        .prefix(&format!(".{}-archive-", crate_name!()))
+                        .suffix(archive_extention)
+                        .tempfile()
+                        .map_err(ArchiveError::TempFile)?,
+                );
+                if let Some(tmp_archive) = &tmp_archive {
+                    // Get the path, and the actual file
+                    let archive_path = tmp_archive.path().to_path_buf();
+                    let archive_file = tmp_archive
+                        .as_file()
+                        .try_clone()
+                        .map_err(ArchiveError::CloneHandle)?;
+
+                    // Select the file name to use if not set
+                    if file_name.is_none() {
+                        // Require user to specify name if multiple files are given
+                        if paths.len() > 1 {
+                            quit_error_msg(
+                                "you must specify a file name for the archive",
+                                ErrorHintsBuilder::default()
+                                    .name(true)
+                                    .verbose(false)
+                                    .build()
+                                    .unwrap(),
+                            );
+                        }
+
+                        // Derive name from given file
+                        file_name = Some(
+                            path.canonicalize()
+                                .map_err(|err| ArchiveError::FileName(Some(err)))?
+                                .file_name()
+                                .ok_or(ArchiveError::FileName(None))?
+                                .to_str()
+                                .map(|s| s.to_owned())
+                                .ok_or(ArchiveError::FileName(None))?,
+                        );
+                    }
+
+                    // Get the current working directory, including working directory as highest possible root, canonicalize it
+                    let working_dir =
+                        current_dir().expect("failed to get current working directory");
+                    let shared_dir = {
+                        let mut paths = paths.clone();
+                        paths.push(working_dir.clone());
+                        match shared_dir(paths) {
+                            Some(p) => p,
+                            None => quit_error_msg(
+                                "when archiving, all files must be within a same directory",
+                                ErrorHintsBuilder::default().verbose(false).build().unwrap(),
+                            ),
+                        }
+                    };
+
+                    // Build an archiver, append each file
+                    let mut archiver = Archiver::new(archive_file);
+                    for path in &paths {
+                        // Canonicalize the path
+                        let mut path = Path::new(path).to_path_buf();
+                        if let Ok(p) = path.canonicalize() {
+                            path = p;
+                        }
+
+                        // Find relative name to share dir, used to derive name from
+                        let name = diff_paths(&path, &shared_dir)
+                            .expect("failed to determine relative path of file to archive");
+                        let name = name.to_str().expect("failed to get file path");
+
+                        // Add file to archiver
+                        archiver
+                            .append_path(name, &path)
+                            .map_err(ArchiveError::AddFile)?;
+                    }
+
+                    // Finish the archival process, writes the archive file
+                    archiver.finish().map_err(ArchiveError::Write)?;
+
+                    // Append archive extention to name, set to upload archived file
+                    if let Some(ref mut file_name) = file_name {
+                        file_name.push_str(archive_extention);
+                    }
+                    path = archive_path;
+                    paths.clear();
+                }
+            }
+        }
+
+        // Quit with error when uploading multiple files or directory, if we cannot archive
+        #[cfg(not(feature = "archive"))]
+        {
+            if paths.len() > 1 {
+                quit_error_msg(
+                    "uploading multiple files is not supported, ffsend must be compiled with 'archive' feature for this",
+                    ErrorHintsBuilder::default()
+                        .verbose(false)
+                        .build()
+                        .unwrap(),
+                );
+            }
+            if path.is_dir() {
+                quit_error_msg(
+                    "uploading a directory is not supported, ffsend must be compiled with 'archive' feature for this",
+                    ErrorHintsBuilder::default()
+                        .verbose(false)
+                        .build()
+                        .unwrap(),
+                );
+            }
+        }
+
         // Create a reqwest client capable for uploading files
         let client_config = create_config(&matcher_main);
         let client = client_config.clone().client(false);
@@ -65,8 +245,6 @@ impl<'a> Upload<'a> {
         select_api_version(&client, host.clone(), &mut desired_version)?;
         let api_version = desired_version.version().unwrap();
 
-        // TODO: ensure the file exists and is accessible
-
         // We do not authenticate for now
         let auth = false;
 
@@ -155,83 +333,6 @@ impl<'a> Upload<'a> {
             }
         };
 
-        // The file name to use
-        #[allow(unused_mut)]
-        let mut file_name = matcher_upload.name().map(|s| s.to_owned());
-
-        // A temporary archive file, only used when archiving
-        // The temporary file is stored here, to ensure it's lifetime exceeds the upload process
-        #[allow(unused_mut)]
-        #[cfg(feature = "archive")]
-        let mut tmp_archive: Option<NamedTempFile> = None;
-
-        #[cfg(feature = "archive")]
-        {
-            // Determine whether to archive, ask if a directory was selected
-            let mut archive = matcher_upload.archive();
-            if !archive && path.is_dir() {
-                if prompt_yes(
-                    "You've selected a directory, only a single file may be uploaded.\n\
-                     Archive the directory into a single file?",
-                    Some(true),
-                    &matcher_main,
-                ) {
-                    archive = true;
-                }
-            }
-
-            // Archive the selected file or directory
-            if archive {
-                eprintln!("Archiving...");
-                let archive_extention = ".tar";
-
-                // Create a new temporary file to write the archive to
-                tmp_archive = Some(
-                    TempBuilder::new()
-                        .prefix(&format!(".{}-archive-", crate_name!()))
-                        .suffix(archive_extention)
-                        .tempfile()
-                        .map_err(ArchiveError::TempFile)?,
-                );
-                if let Some(tmp_archive) = &tmp_archive {
-                    // Get the path, and the actual file
-                    let archive_path = tmp_archive.path().to_path_buf();
-                    let archive_file = tmp_archive
-                        .as_file()
-                        .try_clone()
-                        .map_err(ArchiveError::CloneHandle)?;
-
-                    // Select the file name to use if not set
-                    if file_name.is_none() {
-                        file_name = Some(
-                            path.canonicalize()
-                                .map_err(|err| ArchiveError::FileName(Some(err)))?
-                                .file_name()
-                                .ok_or(ArchiveError::FileName(None))?
-                                .to_str()
-                                .map(|s| s.to_owned())
-                                .ok_or(ArchiveError::FileName(None))?,
-                        );
-                    }
-
-                    // Build an archiver and append the file
-                    let mut archiver = Archiver::new(archive_file);
-                    archiver
-                        .append_path(file_name.as_ref().unwrap(), &path)
-                        .map_err(ArchiveError::AddFile)?;
-
-                    // Finish the archival process, writes the archive file
-                    archiver.finish().map_err(ArchiveError::Write)?;
-
-                    // Append archive extention to name, set to upload archived file
-                    if let Some(ref mut file_name) = file_name {
-                        file_name.push_str(archive_extention);
-                    }
-                    path = archive_path;
-                }
-            }
-        }
-
         // Build the progress reporter
         let progress_reporter: Arc<Mutex<ProgressReporter>> = progress_bar;
 
@@ -376,6 +477,74 @@ impl<'a> Upload<'a> {
     }
 }
 
+/// Find the deepest directory all given paths share.
+///
+/// This function canonicalizes the paths, make sure the paths exist.
+///
+/// Returns `None` if paths are using a different root.
+///
+/// # Examples
+///
+/// If the following paths are given:
+///
+/// - `/home/user/git/ffsend/src`
+/// - `/home/user/git/ffsend/src/main.rs`
+/// - `/home/user/git/ffsend/Cargo.toml`
+///
+/// The following is returned:
+///
+/// `/home/user/git/ffsend`
+#[cfg(feature = "archive")]
+fn shared_dir(paths: Vec<PathBuf>) -> Option<PathBuf> {
+    // Any path must be given
+    if paths.is_empty() {
+        return None;
+    }
+
+    // Build vector
+    let c: Vec<Vec<PathBuf>> = paths
+        .into_iter()
+        .map(|p| p.canonicalize().expect("failed to canonicalize path"))
+        .map(|mut p| {
+            // Start with parent if current path is file
+            if p.is_file() {
+                p = match p.parent() {
+                    Some(p) => p.to_path_buf(),
+                    None => return vec![],
+                };
+            }
+
+            // Build list of path buffers for each path component
+            let mut items = vec![p];
+            #[allow(mutable_borrow_reservation_conflict)]
+            while let Some(item) = items.last().unwrap().parent() {
+                items.push(item.to_path_buf());
+            }
+
+            // Reverse as we built it in the wrong order
+            items.reverse();
+            items
+        })
+        .collect();
+
+    // Find the index at which the paths are last shared at by walking through indices
+    let i = (0..)
+        .take_while(|i| {
+            // Get path for first item, stop if none
+            let base = &c[0].get(*i);
+            if base.is_none() {
+                return false;
+            };
+
+            // All other paths must equal at this index
+            c.iter().skip(1).all(|p| &p.get(*i) == base)
+        })
+        .last();
+
+    // Find the shared path
+    i.map(|i| c[0][i].to_path_buf())
+}
+
 #[derive(Debug, Fail)]
 pub enum Error {
     /// Selecting the API version to use failed.
diff --git a/src/cmd/matcher/upload.rs b/src/cmd/matcher/upload.rs
index 35ce5120ffe170d6c28e8ed8f8eba2238c37e017..b9a9d89a72b90b7d6395d3c6e4949c62a214c4d7 100644
--- a/src/cmd/matcher/upload.rs
+++ b/src/cmd/matcher/upload.rs
@@ -18,10 +18,11 @@ pub struct UploadMatcher<'a> {
 impl<'a: 'b, 'b> UploadMatcher<'a> {
     /// Get the selected file to upload.
     // TODO: maybe return a file or path instance here
-    pub fn file(&'a self) -> &'a str {
+    pub fn files(&'a self) -> Vec<&'a str> {
         self.matches
-            .value_of("FILE")
+            .values_of("FILE")
             .expect("no file specified to upload")
+            .collect()
     }
 
     /// The the name to use for the uploaded file.
diff --git a/src/cmd/subcmd/upload.rs b/src/cmd/subcmd/upload.rs
index 4c39d7ecb0a9e3ad9b6d567583d880d5adbbd5f1..b62ad2d82d2f4ed9ac24171c380464e8555482d3 100644
--- a/src/cmd/subcmd/upload.rs
+++ b/src/cmd/subcmd/upload.rs
@@ -16,9 +16,9 @@ impl CmdUpload {
             .visible_alias("up")
             .arg(
                 Arg::with_name("FILE")
-                    .help("The file to upload")
+                    .help("The file(s) to upload")
                     .required(true)
-                    .multiple(false),
+                    .multiple(true),
             )
             .arg(ArgPassword::build().help("Protect the file with a password"))
             .arg(ArgGenPassphrase::build())
diff --git a/src/util.rs b/src/util.rs
index d3ecb6f142ccbd4b8dfc7a07bb8e376a2b8a7593..67176be26c91be1ab5ba3203a4db2c9775bf9eb2 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -132,6 +132,9 @@ pub struct ErrorHints {
     /// A list of info messages to print along with the error.
     info: Vec<String>,
 
+    /// Show about the name option.
+    name: bool,
+
     /// Show about the password option.
     password: bool,
 
@@ -157,7 +160,8 @@ impl ErrorHints {
     pub fn any(&self) -> bool {
         // Determine the result
         #[allow(unused_mut)]
-        let mut result = self.password || self.owner || self.force || self.verbose || self.help;
+        let mut result =
+            self.name || self.password || self.owner || self.force || self.verbose || self.help;
 
         // Factor in the history hint when enabled
         #[cfg(feature = "history")]
@@ -189,6 +193,12 @@ impl ErrorHints {
                 highlight("--api <VERSION>")
             );
         }
+        if self.name {
+            eprintln!(
+                "Use '{}' to specify a file name",
+                highlight("--name <NAME>")
+            );
+        }
         if self.password {
             eprintln!(
                 "Use '{}' to specify a password",
@@ -230,6 +240,7 @@ impl Default for ErrorHints {
         ErrorHints {
             api: false,
             info: Vec::new(),
+            name: false,
             password: false,
             owner: false,
             #[cfg(feature = "history")]