Practical Linux seccomp Sandboxing for Services with systemd
Linux services often run with far more privileges than they need. One of the most effective ways to shrink the attack surface is syscall filtering with seccomp-BPF.
seccomp (secure computing mode) lets you define an allowlist (or denylist) of system calls a process is permitted to make. Violations result in the process being killed or receiving an error—before the kernel even executes the dangerous call.
This post shows how to apply it practically with systemd's built-in support and the libseccomp library.
Why seccomp matters
Modern Linux kernels expose hundreds of syscalls. A typical web server or database only needs a small subset. By restricting everything else you:
- Block many privilege-escalation and container-escape techniques
- Make exploits that rely on
execve,ptrace,mount, orkeyctlfail immediately - Get auditable violations via the kernel's audit subsystem
Docker and Podman already ship default seccomp profiles. systemd brings the same power to native services.
Using SystemCallFilter= in systemd (easiest path)
systemd exposes seccomp through the SystemCallFilter= directive (and related settings). No code changes required.
Basic example
Create a drop-in for an existing service (e.g., nginx):
sudo systemctl edit nginx.service
Add:
[Service]
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
NoNewPrivileges=yes
@system-service is a predefined group that covers the common needs of most daemons (see systemd-analyze syscall-filter for the full list).
Tighter, profiled allowlist
For a more restrictive profile, first observe what the service actually uses:
sudo strace -f -e trace=all -o /tmp/nginx.trace nginx -g 'daemon off;'
# or use:
sudo systemd-analyze syscall-filter --json | jq
Then create a minimal filter:
[Service]
SystemCallFilter=~@obsolete @cpu-emulation @debug @keyring @mount @raw-io @reboot @swap @obsolete
SystemCallFilter=@system-service
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
The ~ prefix means "deny these groups". Combine with an explicit allowlist for defense-in-depth.
Verification
After reload:
systemctl daemon-reload
systemctl restart nginx
journalctl -u nginx -xe | grep -i seccomp
Successful violations appear as SECCOMP audit messages.
Using libseccomp for custom applications
When you control the source, libseccomp gives fine-grained control and works inside the application itself (after dropping privileges).
Minimal C example
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
if (!ctx) return 1;
// Allow only the syscalls we actually need
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
if (seccomp_load(ctx) != 0) {
seccomp_release(ctx);
return 1;
}
printf("Hello from a seccomp-restricted process\n");
seccomp_release(ctx);
return 0;
}
Compile with -lseccomp.
Any attempt to call open, socket, execve, etc. will immediately terminate the process with "Bad system call".
Production patterns
- Apply the filter after initialization and privilege dropping, but before entering the main event loop.
- Use
SCMP_ACT_ERRNO(EPERM)for some calls instead ofKILLso glibc fallbacks don't crash the process. - Log violations via auditd (
journalctl _AUDIT_TYPE_NAME=SECCOMP).
See the excellent write-up at https://thomastrapp.com/blog/securing-a-cpp-websocket-server-with-libseccomp/ for a real-world websocket server example.
Combining with other systemd hardening
seccomp works beautifully alongside:
-
CapabilityBoundingSet=andAmbientCapabilities= -
ProtectSystem=,ProtectHome=,PrivateTmp= NoNewPrivileges=yes-
RestrictAddressFamilies=,RestrictRealtime=
Run systemd-analyze security yourservice.service before and after to see the score improvement.
Sources & further reading
- systemd.exec(5) — https://man7.org/linux/man-pages/man5/systemd.exec.5.html
- seccomp(2) — https://man7.org/linux/man-pages/man2/seccomp.2.html
- Docker seccomp profiles — https://docs.docker.com/engine/security/seccomp/
- libseccomp GitHub — https://github.com/seccomp/libseccomp
- Red Hat: Mastering systemd security features — https://www.redhat.com/en/blog/mastering-systemd
Start with SystemCallFilter=@system-service on a non-critical service, measure the impact, then tighten further. The reduction in exposed kernel surface is immediate and measurable.
Top comments (0)