systemd can put a service in a namespace with a read-only filesystem, no network, no new privileges and a syscall filter — all from the unit file. For a service you did not write, this is the cheapest security work available.
1. Start from the analyser
systemd-analyze security nginx.service
It scores every unit on the system and lists what is missing. The score is a heuristic, not a target, but it points at the directives worth adding.
2. The directives that carry most of the weight
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
ProtectSystem=strict makes the entire filesystem read-only except /dev, /proc and /sys. Anything the service legitimately writes gets an explicit exception:
ReadWritePaths=/var/lib/myservice /var/log/myservice
3. Test the sandbox, not just the syntax
A tightened unit that starts is not a tightened unit that works. Exercise the paths that write files, rotate logs and reload configuration, then read the journal for EPERM. A syscall filter usually fails at the least convenient moment — during a reload, hours after the deploy.
4. Drop the ones you cannot justify
Every directive here has a cost in debuggability. If a service needs raw sockets, say so in a comment next to the exception rather than removing the whole line. The unit file is documentation of what the service is allowed to do, and it should read that way.