Automation

Set Up Real-Time File Sync with Rsync and Lsyncd on Linux

Keeping files synchronized across multiple servers is one of those tasks that sounds simple until you need it to happen automatically, in real time, without babysitting a cron job. Rsync handles the heavy lifting of efficient file transfer, and lsyncd watches for filesystem changes and triggers rsync the moment something changes. Together, they give you a lightweight, real-time file mirroring setup that works over SSH with minimal overhead.

Original content from computingforgeeks.com - post 66480

This guide walks through setting up rsync for both local and remote file synchronization, then layering lsyncd on top for automatic, event-driven mirroring between two Linux servers. Everything is tested on Rocky Linux 10 and Ubuntu 24.04, with rsync’s core options covered alongside the full lsyncd configuration and systemd integration. If you need something more robust for backups specifically, consider pairing this with rsync backup schedules using systemd timers.

Tested March 2026 | Rocky Linux 10.1 (rsync 3.4.1, lsyncd 2.3.1) and Ubuntu 24.04.4 LTS (rsync 3.2.7, lsyncd 2.2.3). SELinux enforcing on Rocky.

Prerequisites

  • Two Linux servers (source and target) with network connectivity between them
  • Root or sudo access on both servers
  • Tested on: Rocky Linux 10.1, Ubuntu 24.04.4 LTS
  • SSH key-based authentication between the servers (covered below)

The examples use 10.0.1.50 as the source server and 10.0.1.51 as the target. Replace these with your actual server IPs.

Set Up SSH Key Authentication

Before any remote sync works, you need passwordless SSH from the source to the target. On the source server, generate an Ed25519 key pair:

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""

Copy the public key to the target server:

ssh-copy-id [email protected]

Verify the connection works without a password prompt:

ssh [email protected] hostname

The expected output is the target server’s hostname with no password prompt. If you need a deeper walkthrough on SSH keys, see using SSH and SSH keys on Debian or Linux Mint.

Install Rsync

Rsync comes pre-installed on most Linux distributions. Verify it’s available:

rsync --version | head -1

On Rocky Linux 10.1:

rsync  version 3.4.1  protocol version 32

On Ubuntu 24.04:

rsync  version 3.2.7  protocol version 31

If it’s missing, install it:

sudo dnf install -y rsync        # Rocky / AlmaLinux / RHEL
sudo apt install -y rsync        # Ubuntu / Debian

Rsync Fundamentals

Rsync uses a delta-transfer algorithm that only sends the differences between source and destination files, making it far more efficient than scp or plain cp for repeated transfers. The flags you’ll use most often:

FlagPurpose
-aArchive mode (preserves permissions, ownership, timestamps, symlinks)
-vVerbose output
-hHuman-readable file sizes
-zCompress data during transfer
--deleteRemove files from destination that no longer exist in source (mirror mode)
--dry-runShow what would happen without making changes
--excludeSkip files matching a pattern
--progressShow per-file transfer progress
--bwlimitLimit bandwidth in KB/s

The Trailing Slash Gotcha

This catches people off guard constantly. A trailing slash on the source path changes rsync’s behavior:

rsync -avh /opt/source/ /opt/backup/

With the trailing slash, rsync copies the contents of /opt/source/ directly into /opt/backup/:

/opt/backup/file1.txt
/opt/backup/file2.txt

Without the trailing slash:

rsync -avh /opt/source /opt/backup/

Rsync copies the directory itself into the destination:

/opt/backup/source/file1.txt
/opt/backup/source/file2.txt

Most of the time you want the trailing slash. When in doubt, use --dry-run first.

Local Sync Example

Create some test data and sync it to a backup directory:

mkdir -p /opt/source /opt/backup
for i in $(seq 1 5); do dd if=/dev/urandom of=/opt/source/file-$i.dat bs=1M count=1 2>/dev/null; done
rsync -avh /opt/source/ /opt/backup/

The output shows each file transferred:

sending incremental file list
file-1.dat
file-2.dat
file-3.dat
file-4.dat
file-5.dat

sent 5.24M bytes  received 111 bytes  10.49M bytes/sec
total size is 5.24M  speedup is 1.00

Mirror Mode with –delete

The --delete flag makes the destination an exact mirror of the source. If you remove a file from the source, it gets removed from the destination too:

rm /opt/source/file-3.dat
rsync -avh --delete /opt/source/ /opt/backup/

Rsync detects the deletion and removes it from the backup:

sending incremental file list
deleting file-3.dat

sent 129 bytes  received 26 bytes  310.00 bytes/sec
total size is 4.19M  speedup is 27,060.03

Use --delete with caution. A --dry-run first can save you from accidental data loss.

Rsync Over SSH

This is where rsync becomes genuinely useful. Syncing files between servers over an encrypted SSH connection, with only the changed bytes going over the wire.

Push Files to a Remote Server

Send files from the source server to the target:

rsync -avhz -e ssh /opt/source/ [email protected]:/opt/remote-backup/

The -z flag compresses data during transfer (useful over WAN links) and -e ssh explicitly specifies SSH as the transport:

sending incremental file list
created directory /opt/remote-backup
./
file-1.dat
file-2.dat
file-4.dat
file-5.dat

sent 4.20M bytes  received 136 bytes  8.39M bytes/sec
total size is 4.19M  speedup is 1.00

Pull Files from a Remote Server

Pull works in the opposite direction. Fetch files from the remote server to a local directory:

rsync -avhz -e ssh [email protected]:/opt/remote-backup/ /opt/pulled-data/

Transfer Progress and Bandwidth Limiting

For large transfers, --progress shows per-file progress. Pair it with --bwlimit to avoid saturating a production link:

rsync -avhz --progress --bwlimit=5000 -e ssh /opt/source/ [email protected]:/opt/remote-backup/

The --bwlimit=5000 caps the transfer at 5000 KB/s (roughly 5 MB/s). Adjust based on your available bandwidth.

sending incremental file list
bigfile.dat
         32.77K   0%    0.00kB/s    0:00:00
      10.49M 100%  117.28MB/s    0:00:00 (xfr#1, to-chk=0/1)

sent 10.49M bytes  received 35 bytes  20.98M bytes/sec
total size is 10.49M  speedup is 1.00

Exclude Patterns

Skip files you don’t want synced, like log files or temporary caches:

rsync -avhz --exclude='*.log' --exclude='.cache' -e ssh /opt/source/ [email protected]:/opt/remote-backup/

Custom SSH Port

If SSH runs on a non-standard port, pass it through the -e flag:

rsync -avhz -e "ssh -p 2222" /opt/source/ [email protected]:/opt/remote-backup/

Install Lsyncd

Lsyncd (Live Syncing Daemon) monitors directories using Linux’s inotify interface and triggers rsync whenever files change. Instead of running rsync on a schedule (like systemd timers), lsyncd reacts to actual filesystem events, syncing changes within seconds.

Ubuntu / Debian

Lsyncd is in the default repositories:

sudo apt update
sudo apt install -y lsyncd

Verify the installed version:

lsyncd --version

Ubuntu 24.04 ships version 2.2.3:

Version: 2.2.3

Rocky Linux / AlmaLinux / RHEL

Lsyncd is not currently available in EPEL 10 or the base Rocky Linux 10 repositories, so you’ll need to build it from the official GitHub repository. Install the build dependencies first:

sudo dnf install -y epel-release
sudo dnf install -y cmake gcc gcc-c++ compat-lua compat-lua-devel make git

Clone the repository and check out the latest release:

cd /tmp
git clone https://github.com/lsyncd/lsyncd.git
cd lsyncd
git checkout v2.3.1

Configure the build with the correct Lua paths (Rocky 10 uses compat-lua which installs to /usr/include/lua-5.1):

cmake . -DCMAKE_INSTALL_PREFIX=/usr \
  -DLUA_INCLUDE_DIR=/usr/include/lua-5.1 \
  -DLUA_LIBRARIES=/usr/lib64/liblua-5.1.so \
  -DLUA_COMPILER=/usr/bin/luac-5.1 \
  -DLUA_EXECUTABLE=/usr/bin/lua-5.1

Compile and install:

make -j$(nproc)
sudo make install

Confirm the installation:

lsyncd --version

You should see version 2.3.1:

Version: 2.3.1

Configure Lsyncd for Local Directory Sync

Start with a simple local sync to understand lsyncd’s configuration before moving to remote sync. Create the required directories:

sudo mkdir -p /etc/lsyncd /var/log/lsyncd /opt/source /opt/local-mirror

Create a few test files in the source directory:

sudo touch /opt/source/file{1..5}.txt

Open the lsyncd configuration file:

sudo vi /etc/lsyncd/lsyncd.conf.lua

Add the following Lua configuration:

settings {
    logfile = "/var/log/lsyncd/lsyncd.log",
    statusFile = "/var/log/lsyncd/lsyncd.status",
    statusInterval = 10,
    nodaemon = false,
}

sync {
    default.rsync,
    source = "/opt/source/",
    target = "/opt/local-mirror/",
    delay = 5,
    rsync = {
        archive = true,
        compress = true,
    },
}

The settings block controls logging and daemon behavior. The sync block defines what to sync: default.rsync uses rsync for local transfers, delay = 5 batches changes within a 5-second window before triggering a sync (reduces overhead for rapid file changes).

Create a Systemd Service

Ubuntu’s package installs an init script, but a proper systemd unit works better on both distros. Create the service file:

sudo vi /etc/systemd/system/lsyncd.service

Add the following:

[Unit]
Description=Live Syncing Daemon
After=network.target

[Service]
ExecStart=/usr/bin/lsyncd -nodaemon /etc/lsyncd/lsyncd.conf.lua
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now lsyncd

Check the status:

sudo systemctl status lsyncd

You should see lsyncd active and the initial sync completed:

● lsyncd.service - Live Syncing Daemon
     Loaded: loaded (/etc/systemd/system/lsyncd.service; enabled)
     Active: active (running)
   Main PID: 6790 (lsyncd)

Mar 30 18:35:08 source-srv lsyncd[6790]: 18:35:08 Normal: --- Startup ---
Mar 30 18:35:08 source-srv lsyncd[6790]: 18:35:08 Normal: recursive full rsync: /opt/source/ -> /opt/local-mirror/
Mar 30 18:35:08 source-srv lsyncd[6790]: 18:35:08 Normal: Startup of /opt/source/ -> /opt/local-mirror/ finished.

Test the live sync by creating a new file:

sudo touch /opt/source/livetest.txt
sleep 8
ls /opt/local-mirror/

The new file appears in the mirror within seconds:

file1.txt  file2.txt  file3.txt  file4.txt  file5.txt  livetest.txt

The lsyncd log confirms the sync:

sudo tail -3 /var/log/lsyncd/lsyncd.log

Output showing the detected change and rsync call:

Mon Mar 30 18:35:35 2026 Normal: Calling rsync with filter-list of new/modified files/dirs
/livetest.txt
/
Mon Mar 30 18:35:35 2026 Normal: Finished a list after exitcode: 0

Configure Lsyncd for Remote Sync Over SSH

This is the real payoff. Lsyncd can push changes to a remote server in real time over SSH, which is exactly what you want for multi-server deployments, web content mirroring, or keeping a warm standby in sync.

On the target server, create the destination directory:

sudo mkdir -p /opt/remote-mirror
sudo chown user:user /opt/remote-mirror

On the source server, update the lsyncd configuration. The key change is switching from default.rsync to default.rsyncssh and adding the host and targetdir parameters:

sudo vi /etc/lsyncd/lsyncd.conf.lua

Replace the contents with:

settings {
    logfile = "/var/log/lsyncd/lsyncd.log",
    statusFile = "/var/log/lsyncd/lsyncd.status",
    statusInterval = 10,
    nodaemon = false,
}

sync {
    default.rsyncssh,
    source = "/opt/source/",
    host = "[email protected]",
    targetdir = "/opt/remote-mirror/",
    delay = 5,
    rsync = {
        archive = true,
        compress = true,
    },
    ssh = {
        port = 22,
    },
}

The host field takes user@hostname format. If lsyncd runs as root (via systemd), make sure root’s SSH key is authorized on the target server. Restart the service:

sudo systemctl restart lsyncd
sudo systemctl status lsyncd

The startup log confirms the remote sync target:

● lsyncd.service - Live Syncing Daemon
     Active: active (running)

Mar 30 18:37:18 source-srv lsyncd[7350]: 18:37:18 Normal: --- Startup ---
Mar 30 18:37:18 source-srv lsyncd[7350]: 18:37:18 Normal: recursive full rsync: /opt/source/ -> [email protected]:/opt/remote-mirror/
Mar 30 18:37:18 source-srv lsyncd[7350]: 18:37:18 Normal: Startup of "/opt/source/" finished: 0

Test it by creating a file on the source and checking the target:

sudo touch /opt/source/remote-test.txt
sleep 8
ssh [email protected] "ls /opt/remote-mirror/"

The file appears on the remote server:

file1.txt  file2.txt  file3.txt  file4.txt  file5.txt  livetest.txt  remote-test.txt

Syncing Multiple Directories

Add multiple sync blocks to mirror different directory trees to the same or different targets:

settings {
    logfile = "/var/log/lsyncd/lsyncd.log",
    statusFile = "/var/log/lsyncd/lsyncd.status",
    statusInterval = 10,
    nodaemon = false,
}

sync {
    default.rsyncssh,
    source = "/var/www/html/",
    host = "[email protected]",
    targetdir = "/var/www/html/",
    delay = 5,
    rsync = {
        archive = true,
        compress = true,
    },
    ssh = {
        port = 22,
    },
}

sync {
    default.rsyncssh,
    source = "/opt/app/config/",
    host = "[email protected]",
    targetdir = "/opt/app/config/",
    delay = 5,
    rsync = {
        archive = true,
        compress = true,
    },
    ssh = {
        port = 22,
    },
}

Exclude Patterns in Lsyncd

Skip temporary files, caches, or version control directories from syncing:

sync {
    default.rsyncssh,
    source = "/opt/source/",
    host = "[email protected]",
    targetdir = "/opt/remote-mirror/",
    delay = 5,
    exclude = {
        "*.tmp",
        "*.log",
        ".git",
        ".cache",
    },
    rsync = {
        archive = true,
        compress = true,
    },
    ssh = {
        port = 22,
    },
}

Monitor and Troubleshoot Lsyncd

Lsyncd writes both a log file and a status file. The status file is particularly useful because it shows the current state of all sync targets and how many changes are pending.

cat /var/log/lsyncd/lsyncd.status

A healthy status file looks like this:

Lsyncd status report at Mon Mar 30 18:35:18 2026

Sync1 source=/opt/source/
There are 0 delays
Filtering:
  nothing.


Inotify watching 1 directories
  1: /opt/source/

“0 delays” means everything is synced. If you see pending delays, lsyncd is waiting for the delay timer before triggering the next rsync batch.

Watch the log in real time while making changes to see sync events as they happen:

sudo tail -f /var/log/lsyncd/lsyncd.log

Error: “Host key verification failed”

This is the most common issue when setting up remote sync. Lsyncd runs as root via systemd, so root’s ~/.ssh/known_hosts must contain the target server’s host key. Fix it by running once as root:

sudo ssh-keyscan -H 10.0.1.51 | sudo tee -a /root/.ssh/known_hosts

Or SSH to the target once as root to accept the key interactively:

sudo ssh [email protected] hostname

Error: “Permission denied” on Target Directory

If lsyncd’s startup rsync fails with “mkdir failed: Permission denied”, the target directory either doesn’t exist or the SSH user doesn’t have write access. Create it on the target and set ownership:

sudo mkdir -p /opt/remote-mirror
sudo chown user:user /opt/remote-mirror

Production Hardening

Increase Inotify Watch Limits

Each directory that lsyncd monitors consumes an inotify watch. The default limit is typically around 8192 to 65536 depending on the distribution. For large directory trees, you’ll hit this limit and lsyncd will silently stop watching new subdirectories. Rocky Linux 10 defaults to 13,389 and Ubuntu 24.04 to 15,052.

Check the current limit:

cat /proc/sys/fs/inotify/max_user_watches

Increase it permanently:

echo "fs.inotify.max_user_watches = 524288" | sudo tee /etc/sysctl.d/99-lsyncd.conf
sudo sysctl -p /etc/sysctl.d/99-lsyncd.conf

Log Rotation

Lsyncd’s log file grows indefinitely. Set up logrotate to prevent disk space issues:

sudo vi /etc/logrotate.d/lsyncd

Add the following rotation policy:

/var/log/lsyncd/*.log {
    weekly
    rotate 4
    compress
    missingok
    notifempty
    postrotate
        systemctl restart lsyncd > /dev/null 2>&1 || true
    endscript
}

Tuning the Delay Parameter

The delay setting in the sync block controls how long lsyncd waits to batch changes before triggering rsync. A lower value (1 to 3 seconds) means faster sync but more rsync processes spawned. A higher value (15 to 30 seconds) batches more changes per sync call, reducing overhead on high-churn directories.

For web content mirroring where near-instant sync matters, delay = 1 works well. For general file sync where a few seconds of lag is acceptable, delay = 10 is a good default. If you’re syncing a directory where hundreds of files change simultaneously (build artifacts, log rotation), bump it to delay = 30 to let lsyncd batch everything into a single rsync call.

Bandwidth Control

Prevent lsyncd from saturating your network by adding bandwidth limits to the rsync configuration:

rsync = {
    archive = true,
    compress = true,
    bwlimit = 10000,
},

The bwlimit value is in KB/s. Set it based on your available bandwidth and how much you’re willing to allocate to sync traffic. For a comparison of other file synchronization approaches, see Syncthing on Rocky Linux which takes a peer-to-peer approach instead of the push model that rsync and lsyncd use.

ItemRocky Linux 10Ubuntu 24.04
rsync version3.4.13.2.7
lsyncd version2.3.1 (built from source)2.2.3 (from repos)
lsyncd install methodcmake + makeapt install lsyncd
SELinux/AppArmorSELinux enforcing, no issuesAppArmor, no issues
Default inotify watches13,38915,052
Firewallfirewalld (port 22 open by default)ufw (disabled by default)

Related Articles

Automation How To Setup Chef Infra Server on CentOS 8 / RHEL 8 Kubernetes How to force delete a Kubernetes Namespace Automation How to add Branch Permissions to a repository in Bitbucket Automation Scan PHP|JavaScript|C#|HTML using Sonar Scanner and Jenkins

Leave a Comment

Press ESC to close