How to preserve ownership and permissions with rsync

Unix services often depend on file metadata as much as file content. A restored configuration tree can contain every expected byte and still fail when its owner, group, mode, or modification time differs from the source.

Archive mode, -a, combines recursive transfer with links, permissions, modification times, groups, owners, devices, and special files. It does not preserve ACLs, extended attributes, access times, creation times, or hard-link relationships; those attributes need separate options when they belong in the restore.

The receiving rsync process needs elevated privileges to assign an arbitrary owner. A non-root receiver owns newly copied files itself and can preserve only groups available to that account, so perform a local restore from an administrative account after checking the destination path and the service identities expected there.

Steps to preserve ownership and permissions with rsync:

  1. Record the source file and directory metadata.
    $ stat -c '%n %U:%G %a %y' /srv/app/config /srv/app/config/app.conf
    /srv/app/config appsvc:appops 750 2026-06-06 03:30:00.000000000 +0000
    /srv/app/config/app.conf appsvc:appops 640 2026-06-06 03:30:00.000000000 +0000
  2. Confirm the destination path, then copy the tree in archive mode with itemized changes.

    A privileged receiver can replace ownership and modes on existing destination entries, which can make a service tree unreadable. Restore into an empty or staged directory when the existing tree must remain available for rollback.

    $ sudo rsync -a --itemize-changes /srv/app/ /srv/restore/app/
    .d..tpog... ./
    cd+++++++++ config/
    >f+++++++++ config/app.conf

    The trailing slash on /srv/app/ copies its contents and applies the source directory attributes to /srv/restore/app/. In itemized output, p, o, and g identify permission, owner, and group changes.

  3. Compare the copied file and directory with the source metadata.
    $ stat -c '%n %U:%G %a %y' /srv/restore/app/config /srv/restore/app/config/app.conf
    /srv/restore/app/config appsvc:appops 750 2026-06-06 03:30:00.000000000 +0000
    /srv/restore/app/config/app.conf appsvc:appops 640 2026-06-06 03:30:00.000000000 +0000
  4. From a shell owned by the non-root backup account, copy the same source into a disposable destination without sudo.
    $ rsync -a --itemize-changes /srv/app/ /tmp/rsync-unprivileged/
    created directory /tmp/rsync-unprivileged
    cd+++++++++ ./
    cd+++++++++ config/
    >f+++++++++ config/app.conf

    The backup account in this comparison can read the source and belongs to appops. The next check shows which metadata it still cannot assign.

  5. Inspect the unprivileged copy.
    $ stat -c '%n %U:%G %a %y' /tmp/rsync-unprivileged/config /tmp/rsync-unprivileged/config/app.conf
    /tmp/rsync-unprivileged/config backup:appops 750 2026-06-06 03:30:00.000000000 +0000
    /tmp/rsync-unprivileged/config/app.conf backup:appops 640 2026-06-06 03:30:00.000000000 +0000

    The modes, times, and permitted group survived, but the owner changed from appsvc to the receiving backup account. Use a privileged receiver when the original owner is required.

  6. Remove the disposable unprivileged copy.
    $ rm -r /tmp/rsync-unprivileged

    Check the exact temporary path before removing it. This cleanup does not touch the restored tree under /srv/restore/app/.

  7. Run a privileged dry run against the retained restore.
    $ sudo rsync -a --dry-run --itemize-changes /srv/app/ /srv/restore/app/

    An exit status of zero with no itemized output means the source data and the metadata preserved by archive mode already match the destination.