System hardening
Restrict files and processes
Use dedicated service users, narrow filesystem permissions, controlled working directories, and systemd sandboxing where the application supports it.
A web application rarely needs access to the whole server. Give its process a small part of the filesystem and no interactive login.
A dedicated service user
Run each service as a dedicated unprivileged user:
sudo adduser --system --group --home /srv/blog blog
The --system flag creates an account with no password and a non-login shell. It exists to own a process, not to be logged into.
Narrow the filesystem
Make code read-only to the process and grant writes only to explicit data directories:
sudo chown -R root:blog /srv/blog/app
sudo chmod -R 750 /srv/blog/app
sudo chown blog:blog /srv/blog/uploads
Now the blog user can read and execute the application but cannot modify it. A compromised web process that can write its own executable can make persistence easy. Read-only code and one writable upload directory reduce damage, but overly strict rules can break legitimate updates.
Let systemd enforce it
The service unit can lock this in at the process level:
[Service]
User=blog
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/srv/blog/uploads
ProtectSystem=strict mounts the entire filesystem read-only for this process, and ReadWritePaths opens exact exceptions. Add systemd protections gradually, test them, and avoid giving application users sudo access.
Apply sandboxing one capability at a time and watch service logs. The goal is verified confinement, not a long directive list that operators disable after an unexplained outage. When a directive breaks the app, the journal usually shows a permission error naming the path — that is your cue to add a ReadWritePaths entry, not to remove the protection.
Prove the confinement
sudo -u blog touch /srv/blog/app/server.js
# touch: cannot touch '/srv/blog/app/server.js': Permission denied
sudo -u blog touch /srv/blog/uploads/probe.txt
# succeeds
Record the service user and every required read or write path. Attempt writes to code, configuration, and the approved data directory, then prove only the intended data write succeeds.
Lesson completed