Service Hardening With systemd
How systemd starts services, why most services should not run as root, and how a few unit settings contain a compromised application.
OpenVAPT Academy handout. Online, with the self-check quiz: https://openvapt.com/academy/p11.html. Offline: openvapt learn P11.
Read First: P1
You Will Be Able To
- Read a systemd unit file and its drop-ins and know which settings win.
- Explain why a service's user, program location and unit file permissions decide what an attacker gains.
- Apply basic sandboxing settings and keep secrets out of unit files.
How a Service Starts
systemd reads a service's unit file from /usr/lib/systemd/system or /lib/systemd/system (packages) or /etc/systemd/system (the administrator), then applies drop-in files from <name>.service.d/*.conf in order. A drop-in therefore overrides the unit file it extends. ExecStart= names the program, User= the account it runs as. With no User= the service runs as root.
Application services written by administrators, such as a Node.js API or a Django site, are the code most exposed to the internet. Running them as root turns any bug in the application into full control of the machine.
Who Can Change What Runs
A service is only as trustworthy as the files that define it. Suppose a normal user can edit the unit file, or replace the program it starts, such as a binary in /tmp or a world-writable file. That user can make systemd run their code as root at the next restart. This is a well-known privilege escalation and persistence technique (MITRE ATT&CK T1543.002 and T1574.010).
Sandboxing in a Few Lines
systemd can confine a service without changing its code. NoNewPrivileges=yes stops it gaining privileges through setuid programs. ProtectSystem=strict mounts the operating system read-only for it. PrivateTmp=yes gives it its own /tmp. ProtectHome=yes hides home directories. ReadWritePaths= then lists the few folders it really needs to write. systemd-analyze security <unit> scores how exposed a unit is and suggests more options.
Secrets Do Not Belong in Unit Files
Unit files are readable by every user, and systemctl show prints a service's Environment= values to anyone. Passwords and tokens belong in a root-only EnvironmentFile= or, better, in systemd credentials (LoadCredential=), which only the service can read.
In Summary
A systemd unit can run a service as a dedicated user inside a sandbox. Each hardening option removes something an attacker could abuse.
Apply It
These OpenVAPT checks put this module into practice. Open one to see its detection logic and source code.
| Check | Title | Severity |
|---|---|---|
HOST-SYSD-001 | Custom Services Run as Root | Medium |
HOST-SYSD-002 | Custom Services Lack Sandboxing | Low |
HOST-SYSD-003 | Service Unit Files Writable by Non-Root Users | High |
HOST-SYSD-004 | Service Programs in Writable Locations | High |
HOST-SYSD-005 | Secrets Written Into Service Unit Files | Medium |
Printable Worksheet · Teacher Answer Key · For Teachers · Print this page with your browser's Print command for a handout.
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Lab
Run: openvapt demo --only HOST-SYSD- --out p11 and list, for each vulnerable unit, the line you would add or change.Compare the shop-api.service unit in the vulnerable and hardened labs (internal/lab/fixtures_stacks.go) setting by setting.On a test machine: systemd-analyze security and pick the service with the worst score; add NoNewPrivileges=yes with systemctl edit and re-run.
Check Yourself
Answers are checked in your browser and never stored or sent.
References
Adapted from the OpenVAPT Academy (openvapt.com), built by AI Cybersecurity Engineers, used under the OpenVAPT Source-Available Licence 1.3. Free teaching use under Teaching With OpenVAPT. Pre-release material may be wrong or unstable; use at your own risk.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). P11: Service Hardening With systemd (Version 0.1.0-alpha.6). OpenVAPT Academy. Retrieved October 16, 2026, from https://openvapt.com/academy/p11.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_p11,
author = {{OpenVAPT}},
title = {{P11: Service Hardening With systemd}},
year = {2026},
howpublished = {\url{https://openvapt.com/academy/p11.html}},
note = {OpenVAPT Academy, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.