Copying files over SSH every time gets old, so rsync can run its own daemon and hand out a directory as a named module instead. You will build and test a small rsync daemon that exports one directory read-only, using rsync 3.2.7 from Ubuntu package rsync 3.2.7-1ubuntu1.5. Allow about 20 minutes, plus time to decide which files may be exposed. The examples use a high TCP port and a temporary configuration so the first test needs no root and does not disturb a system service.
You need the rsync command, a directory containing test data, and permission to read that directory.
Security boundary: the daemon protocol provides authentication but does not encrypt transferred data. Do not expose a password-protected daemon directly to an untrusted network unless you have added a separate TLS proxy or chosen SSH transport instead.
Confirm the executable and version before copying configuration examples:
$ command -v rsync
/usr/bin/rsync
$ rsync --version | head -1
rsync version 3.2.7 protocol version 31
$ rsync --daemon --help | grep -E -- '--config|--no-detach|--port'
--config=FILE specify alternate rsyncd.conf file
--no-detach do not detach from the parent
--port=PORT listen on alternate port number
The configuration file is line-based. A module starts with a name in square brackets, followed by name = value parameters. A hash is a comment only when it is the first non-whitespace character. Boolean values may be written as yes or no, 0 or 1, and true or false.
Use your own paths in this example. The commands below create a disposable test tree under /tmp and a configuration beside it. This is ordinary user work and needs no sudo:
$ TEST_ROOT=/tmp/rsyncd-guide
$ mkdir -p "$TEST_ROOT/export"
$ printf '%s\n' 'rsync daemon smoke test' > "$TEST_ROOT/export/readme.txt"
$ umask 077
$ cat > "$TEST_ROOT/rsyncd.conf" <<'EOF'
port = 1873
address = 127.0.0.1
use chroot = no
log file = /tmp/rsyncd-guide/rsyncd.log
pid file = /tmp/rsyncd-guide/rsyncd.pid
[public]
path = /tmp/rsyncd-guide/export
comment = temporary read-only test module
read only = yes
list = yes
EOF
$ chmod 600 "$TEST_ROOT/rsyncd.conf"
The module is called public, but it is not actually public on the network because the daemon listens only on loopback. The daemon's default port is 873, which is below 1024 and normally needs root; port 1873 keeps this test unprivileged. read only = yes is also the documented default, but writing it explicitly makes the safety boundary visible during review.
Checkpoint: inspect the file before starting anything:
$ sed -n '1,40p' "$TEST_ROOT/rsyncd.conf"
port = 1873
address = 127.0.0.1
use chroot = no
log file = /tmp/rsyncd-guide/rsyncd.log
pid file = /tmp/rsyncd-guide/rsyncd.pid
[public]
path = /tmp/rsyncd-guide/export
comment = temporary read-only test module
read only = yes
list = yes
Start a second terminal, set the same TEST_ROOT value, and run the daemon without detaching:
$ TEST_ROOT=/tmp/rsyncd-guide
$ rsync --daemon --no-detach --config="$TEST_ROOT/rsyncd.conf"
A foreground daemon normally prints nothing while it waits for clients. Leave this terminal open. The process reads rsyncd.conf for each client connection, so a configuration edit is not a reason to send it HUP. Stop this test daemon with Ctrl-C once the checks below are done. Do not use this foreground command as a production service definition.
If it exits immediately, read the error, check that port 1873 is free, and verify that the configured paths exist and are readable. A daemon can run without root only when its files, logs and lock files are accessible to the account running it.
In the first terminal, ask the daemon for its module list:
$ rsync rsync://127.0.0.1:1873/
public temporary read-only test module
Now copy the file to a separate destination. The client command is unprivileged:
$ mkdir -p "$TEST_ROOT/download"
$ rsync -av rsync://127.0.0.1:1873/public/readme.txt "$TEST_ROOT/download/"
receiving incremental file list
readme.txt
sent ... bytes received ... bytes ... bytes/sec
total size is ... speedup is ...
$ cat "$TEST_ROOT/download/readme.txt"
rsync daemon smoke test
Exact byte counts and speed figures vary. The result that matters is that the module is reachable and the downloaded file has the expected content. The daemon log should also exist:
$ test -s "$TEST_ROOT/rsyncd.log" && echo 'log written'
log written
Do not test writability by uploading over a valuable file. Create a harmless local file and target a new name:
$ printf '%s\n' 'should not arrive' > "$TEST_ROOT/attempt.txt"
$ rsync "$TEST_ROOT/attempt.txt" rsync://127.0.0.1:1873/public/
rsync: ... read only ...
The exact error text depends on where the daemon rejects the request, but the command must return non-zero and attempt.txt must not appear in the export directory. Check both:
$ test ! -e "$TEST_ROOT/export/attempt.txt" && echo 'upload rejected'
upload rejected
$ test ! -e "$TEST_ROOT/export/attempt.txt"
Warning: if an upload succeeds, stop the daemon immediately with Ctrl-C. Check the module's read only setting, look for a per-user auth users rule that grants rw, and do not expose the service until the unexpected write is explained.
The test above is anonymous. For a real daemon, add auth users and secrets file to the module, then protect the secrets file so only the daemon account can read it:
[archive]
path = /srv/rsync/archive
read only = yes
auth users = backup
secrets file = /etc/rsyncd.secrets
# elevated: create a password file readable only by its owner
$ sudo install -o root -g root -m 600 /dev/null /etc/rsyncd.secrets
$ sudo sh -c 'printf "%s\n" "backup:REPLACE_WITH_A_LONG_RANDOM_PASSWORD" > /etc/rsyncd.secrets'
The file stores a username and password separated by a colon. Do not put a real password in shell history or this article's example. The daemon uses a challenge-response exchange, but the protocol does not encrypt the file data or provide strong modern transport security. Bind it to a restricted address and firewall it, or put it behind TLS. If you need encryption without a daemon protocol, use rsync over SSH instead.
Recovery: to undo this example, remove the test daemon with Ctrl-C and delete only the disposable tree after checking its path:
$ test "$TEST_ROOT" = /tmp/rsyncd-guide
$ rm -rf -- "$TEST_ROOT"
Destructive action: that removal is irreversible. Do not adapt the command to a production directory.