Serve a Read-Only rsync Module from rsyncd.conf

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.

1. Check the installed command

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.

2. Prepare a directory and temporary configuration

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

3. Start the daemon in the foreground

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.

4. List the module and download a file

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

5. Prove that the module rejects uploads

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.

6. Add authentication before using a network address

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.

Done means