Restrict an SSH Key to rsync with rrsync
You will set up one SSH public key that can run rsync only inside a chosen directory, with either read-only or write-only access. The examples use rrsync from rsync 3.2.7, installed here as package version 3.2.7-1ubuntu1.5. Allow about fifteen minutes if the SSH key already exists. This guide changes an account's ~/.ssh/authorized_keys, so keep a separate administrative login available before testing.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the installed rrsync
Run these ordinary, read-only checks on the server that will receive the SSH connection:
$ command -v rrsync
/usr/bin/rrsync
$ rsync --version | head -1
rsync version 3.2.7 protocol version 31
$ rrsync -help
usage: rrsync [-ro | -wo] [-munge] [-no-del] [-no-lock] [-help] DIR
The exact help text can vary slightly with packaging, but the installed command accepts one directory and the options shown above. The directory may be relative to the restricted user's home directory or an absolute path. The examples below use a directory relative to that home directory because it avoids a second set of ownership and traversal assumptions.
Checkpoint: do not continue until command -v rrsync returns the program you intend to call. If it returns nothing, stop and install or expose the rsync package through your normal system-management process. Do not put a guessed path into authorized_keys.
2. Prepare the restricted directory
Choose the account and directory first. In this example the account is backupdrop and the restricted directory is incoming beneath its home directory. The account and directory must already exist before the key is used:
$ getent passwd backupdrop
backupdrop:x:...:/home/backupdrop:/bin/sh
$ ls -ld /home/backupdrop/incoming
drwx------ ... backupdrop backupdrop /home/backupdrop/incoming
The output is host-specific. The useful checks are that the account exists, the directory is the one you intended, and the SSH account can read or write it as required. A directory name in the forced command is not a label: incoming means the path beneath the account's home directory.
If the directory does not exist, create it with the account's normal administration process. This is a state-changing operation and normally needs elevated privileges:
$ sudo install -d -o backupdrop -g backupdrop -m 0700 /home/backupdrop/incoming
Undo that preparation only when you are certain no data is needed: sudo rmdir /home/backupdrop/incoming removes the directory only if it is empty. Do not use a recursive removal as part of this setup.
3. Back up the SSH key file
Before editing the account, make a private backup of its key file. Run this as the account owner or with the minimum privilege needed to read the file:
$ install -m 0600 /home/backupdrop/.ssh/authorized_keys /home/backupdrop/.ssh/authorized_keys.before-rrsync
$ ls -l /home/backupdrop/.ssh/authorized_keys*
If the file does not exist, create it with the SSH directory's existing ownership and mode policy instead of copying this command blindly. The backup contains public keys and SSH restrictions, but it still reveals access configuration. Keep it readable only by the account owner and administrators.
Checkpoint: confirm that your normal administrator key still appears in the file. A malformed replacement can lock out the restricted account, while a misplaced restriction can affect the wrong key.
4. Add a read-only key
Append one line for the client key that should be allowed to download files. The forced command is the part that invokes rrsync; replace the key material, not the command syntax:
command="rrsync -ro incoming" ssh-ed25519 AAAA_REPLACE_WITH_THE_CLIENT_PUBLIC_KEY client@example
Replace the AAAA_REPLACE_WITH_THE_CLIENT_PUBLIC_KEY text with the complete public key from the client, including its real type and optional comment. Do not paste a private key into this file. The -ro option permits reading from incoming and implies -no-del and -no-lock. It therefore also blocks rsync deletion and removal options.
For a write-only drop location, use a separate key and a separate line:
command="rrsync -wo incoming" ssh-ed25519 AAAA_REPLACE_WITH_A_DIFFERENT_CLIENT_PUBLIC_KEY uploader@example
-wo allows only writing to the directory. Do not assume it is interchangeable with read-only access: test it with the client workflow you actually need. One rrsync key has one restricted directory. If you need several independent directories or module-level configuration, the daemon-over-SSH arrangement described in the manpage is a different design.
5. Test the forced command through SSH
Test from the client using the matching private key. Use a temporary destination so a successful read test cannot overwrite a useful local directory:
$ mkdir -p /tmp/rrsync-read-test
$ rsync -av -e 'ssh -i /path/to/client_key' [email protected]:incoming/ /tmp/rrsync-read-test/
receiving incremental file list
sent ... bytes received ... bytes ... bytes/sec
total size is ... speedup is ...
The transfer summary is illustrative because filenames and byte counts depend on the directory. The useful result is a successful rsync transfer from the intended directory. The SSH session must not provide an interactive shell: the key's forced command receives rsync's server request and rrsync validates it.
Checkpoint: verify the client sees only the intended files. A successful transfer does not prove that every path spelling is safe, so test a file in the restricted directory and do not add a second key with a broader command while debugging.
6. Verify the write-only boundary
For the -wo key, upload a disposable file to a test directory and check the result on the server:
$ printf '%s\n' 'rrsync write test' > /tmp/rrsync-upload.txt
$ rsync -av -e 'ssh -i /path/to/uploader_key' /tmp/rrsync-upload.txt [email protected]:incoming/
$ ssh -i /path/to/uploader_key [email protected]
rrsync: restricted command rejected
$ rm -f /tmp/rrsync-upload.txt
The final SSH command is expected to fail because this key is forced to rrsync, not a shell. The exact diagnostic and exit status depend on the SSH and rsync versions. Check the server directory through an administrative account, not by weakening the forced command:
$ sudo ls -l /home/backupdrop/incoming/rrsync-upload.txt
-rw------- ... backupdrop backupdrop ... /home/backupdrop/incoming/rrsync-upload.txt
Do not use --delete as a test. The read-only configuration rejects delete and remove options, and destructive synchronisation can remove data when the source selection is wrong.
7. Handle path and shell traps
rrsync validates the paths it receives and rejects --copy-links by default, preventing rsync from following a symlink inside the restricted directory to a file outside it. It also rejects --protect-args (-s) because the server-side script cannot inspect the options hidden by that mode. If a workflow genuinely requires protect-args, use the daemon-over-SSH configuration instead of weakening this restriction.
Do not add -no-lock casually. The normal rrsync lock check prevents multiple runs for the same user; the option is mainly useful with -munge. The -munge option enables rsync's --munge-links on the server side, which is a deliberate symlink-handling choice rather than a general repair option.
Finally, check the restricted user's login shell. The rrsync manpage warns that bash can run user-controlled startup files before a forced command. If that account can modify its home bashrc files, the forced-command boundary may be undermined. Use a simpler shell such as dash only after checking the account's existing access and your operating system's shell policy; changing a login shell is security-sensitive and can disrupt other access.
8. Recover from a bad key line
If the test fails because of an editing mistake, restore the key file only after confirming the target path and keeping your administrative session open:
$ sudo cp --preserve=mode,ownership,timestamps /home/backupdrop/.ssh/authorized_keys.before-rrsync /home/backupdrop/.ssh/authorized_keys
$ sudo ssh-keygen -lf /home/backupdrop/.ssh/authorized_keys
The fingerprint command may report that the file contains multiple lines or non-key options, so its output is not a complete configuration test. Reopen the file and compare the restored contents with the backup. Once the corrected line is installed, repeat the read-only transfer test before allowing production traffic.
Done means
rrsyncand rsync are the expected installed 3.2.7 tools.- The forced command names one intended directory and one deliberate access mode.
- The client can transfer the expected files and cannot open an interactive shell with that key.
- Read-only keys reject deletion, and write-only keys are tested with disposable data.
- The original
authorized_keysfile is backed up and a recovery path remains available. - The account's shell and symlink behaviour have been checked before treating the restriction as a security boundary.