Skip to content

Run Redis as an unprivileged user

Severity: mediumApplies to: Redis 6.x+Applies to: Redis 8.xApplies to: Valkey 8.x / 9.x
The fix/etc/systemd/system/redis-server.service.d/user.conf
[Service]
User=redis
Group=redis
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/redis /var/log/redis /run/redis
Terminal window
sudo systemctl daemon-reload
sudo systemctl restart redis-server

Distribution packages already run as redis (or valkey). This page mostly matters for hand-rolled installs and containers.

Redis’s own documentation states plainly that it “does not require root privileges to run” and recommends an unprivileged redis user.

The reason is specific rather than general hygiene. Redis can be made to write files to arbitrary paths — CONFIG SET dir plus CONFIG SET dbfilename plus SAVE writes the dataset wherever you point it. That is the standard exploitation path for an exposed instance.

The user Redis runs as decides what that write can reach:

  • As redis: the attacker writes files owned by redis, in places redis can write. Bad, and often enough to pivot — but bounded.
  • As root: the same write lands in /root/.ssh/authorized_keys, or /etc/cron.d/, or a systemd unit. That is not a pivot, it is the machine.

Same bug, same command, entirely different blast radius. This control doesn’t stop the write; it decides what the write costs you.

The systemd hardening directives are the free part

Section titled “The systemd hardening directives are the free part”

User=redis is the essential line. The rest above are cheap additions that narrow the same write further:

Directive Effect
NoNewPrivileges The process cannot gain privileges via setuid
ProtectSystem=strict The entire filesystem is read-only except ReadWritePaths
ProtectHome /home, /root and /run/user are inaccessible
PrivateTmp Private /tmp, so no tmp-based interference

ProtectSystem=strict with an explicit ReadWritePaths is the one that pays. It means even a successful CONFIG SET dir /etc/cron.d cannot write, because the path is read-only to the process regardless of file permissions.

USER redis in the Dockerfile, or user: redis in compose. Running Redis as root inside a container is common and is a smaller problem than on a host — but combined with a bind mount it reaches the host filesystem as root, which puts you straight back to the worst case.