Some file-distribution jobs need a pull-only endpoint without giving clients shell accounts. The rsync daemon exposes named modules on TCP port 873, which suits release trees and internal mirrors used from a trusted LAN or VPN.
On Ubuntu, the packaged rsync.service starts only when /etc/rsyncd.conf exists. A module combines its exported path with a low-privilege user and group, chroot confinement, a read-only policy, and client address rules; filesystem permissions should reinforce those controls.
Native rsync:// traffic is not protected by SSH encryption. Restrict port 873 to the required client network, keep internet-facing transfers behind a VPN or TLS proxy, and use rsync over SSH when clients already have managed shell access.
$ sudo install -d -o root -g nogroup -m 0750 /srv/rsync/archives
Related: How to install rsync on Ubuntu
$ sudoedit /srv/rsync/archives/release.txt
/srv/rsync/archives/release.txt release 2026.07
$ sudo chown -R root:nogroup /srv/rsync/archives
$ sudo chmod -R u=rwX,g=rX,o= /srv/rsync/archives
Tool: chmod Calculator
$ sudo cp --archive /etc/rsyncd.conf /etc/rsyncd.conf.backup
Skip the backup command when /etc/rsyncd.conf does not exist yet. Restore this copy and restart rsync.service if the new module prevents the service from starting.
$ sudoedit /etc/rsyncd.conf
/etc/rsyncd.conf
[archives]
path = /srv/rsync/archives
comment = Release archive
read only = yes
list = yes
uid = nobody
gid = nogroup
use chroot = yes
hosts allow = 192.0.2.0/24
hosts deny = *
Replace 192.0.2.0/24 with the LAN or VPN subnet that should reach the module. Keep hosts deny = * so every source outside the allow list is rejected.
use chroot = yes requires the packaged daemon to start as root, then each transfer switches to nobody:nogroup for file access inside the module path.
$ sudo ufw allow from 192.0.2.0/24 to any port 873 proto tcp
Do not add a public any-source rule for port 873. Use the firewall that already manages the server when ufw is not active.
$ sudo systemctl enable rsync
$ sudo systemctl restart rsync
If the command fails, inspect systemctl status rsync and journalctl -u rsync before changing the firewall or client.
$ rsync rsync://mirror.example.com/ archives Release archive
$ rsync -av rsync://mirror.example.com/archives/ ./archives-test/ receiving incremental file list created directory ./archives-test ./ release.txt sent 46 bytes received 135 bytes 362.00 bytes/sec total size is 16 speedup is 0.09
The trailing slash after archives/ copies the module contents into ./archives-test/ rather than creating another directory level.
$ cat ./archives-test/release.txt release 2026.07
$ rsync -av ./upload-probe.txt rsync://mirror.example.com/archives/ sending incremental file list ERROR: module is read only rsync error: syntax or usage error (code 1) at main.c(1168) [Receiver=3.4.1] rsync: [sender] read error: Connection reset by peer (104)
$ rsync rsync://mirror.example.com/archives/ @ERROR: access denied to archives from UNKNOWN (198.51.100.24) rsync error: error starting client-server protocol (code 5) at main.c(1872) [Receiver=3.4.1]
The rejected address must belong to a real client outside the configured allow list. Do not change hosts allow merely to manufacture this check.