
August 22, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Most production compromises do not stop at a single bug. Attackers chain syscalls like ptrace, mount, or unshare to move from a PHP flaw to host access. seccomp breaks that chain at the kernel. It uses BPF filters to allow only the syscalls your process needs. In my experience hardening web servers and VPS hosts in Nepal, seccomp delivers more real protection than many application-layer add-ons, with almost no runtime cost.
security_opt, Kubernetes Pod Security Standards, or systemd SystemCallFilter on PHP-FPM and queue workers.What is seccomp and how does it restrict syscalls for security?
Seccomp (Secure Computing Mode) is a Linux kernel feature. Every thread can run under a syscall policy. When code calls a blocked syscall, the kernel returns an error or sends SIGSYS before dangerous kernel work runs.
Two modes matter in production. SECCOMP_MODE_STRICT allows only read, write, _exit, and sigreturn. It is too tight for PHP or Node.js. SECCOMP_MODE_FILTER loads a BPF program that evaluates each syscall number and optional arguments. That is what Docker, containerd, and systemd use today.
The filter sits between userspace and the kernel. Application code cannot disable it without a separate privilege escalation. Memory corruption, deserialisation bugs, or shell uploads still run attacker code. They cannot call mount to escape a container or bpf to install a new filter. This is defence in depth, not a replacement for patches, WAF rules, or input validation.
On Ubuntu 22.04 and 24.04 servers running PHP 8.3 or 8.4, seccomp pairs well with namespaces, dropped capabilities, and read-only root filesystems. I apply it to Laravel queue workers, PHP-FPM pools, and sidecar containers on the same hosts I manage through Linux system administration engagements. The official kernel documentation at docs.kernel.org seccomp remains the authoritative reference for filter semantics.
How do you build and test a seccomp profile without breaking production?
Guessing syscall lists causes outages. Tracing real workloads produces a baseline you can tighten over time. The workflow below is what I use before rolling seccomp to client-facing services.
Step 1: Capture syscalls with strace
Run representative traffic on staging first. Cover logins, file uploads, queue jobs, failed payments, and cache misses. A single happy-path trace is never enough.
sudo strace -f -e trace=%syscall -o /tmp/syscalls.log \
php artisan queue:work --once
grep -oP '(?<=^[0-9]+ )[a-z_0-9]+' /tmp/syscalls.log \
| sort -u > /tmp/allowed_syscalls.txt
wc -l /tmp/allowed_syscalls.txt Typical Laravel workers on PHP 8.4 need roughly 40–80 unique syscalls. Symfony and WordPress counts differ once image processing or shell commands enter the path. Compare staging traces after every major PHP extension or kernel upgrade.
Step 2: Start from Docker's default profile
Do not build JSON from scratch on day one. Docker ships a maintained default profile that already blocks many dangerous calls. Trim or extend it after you know your app's needs. The profile lives in the Moby repository and is documented in Docker's seccomp guide.
curl -fsSL \
https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json \
-o seccomp-default.json
jq '.syscalls += [{
"names": ["io_uring_enter"],
"action": "SCMP_ACT_ALLOW"
}]' seccomp-default.json > seccomp-laravel-worker.json Validate JSON before deploy. A syntax error silently falls back to Docker defaults or rejects the container start, depending on runtime settings.
Step 3: Watch for blocked syscalls in audit logs
When a legitimate call gets denied, you need the syscall name, not a vague 500 error. Linux audit rules make that visible.
echo '-a always,exit -F arch=b64 -S all -F success=0 -k seccomp-deny' \
| sudo tee /etc/audit/rules.d/seccomp.rules
sudo augenrules --load
sudo ausearch -k seccomp-deny --interpret | tail -20 Map numeric IDs with ausyscall --dump or scmp_sys_resolver from libseccomp. Add the missing name to your allow list, redeploy to staging, and repeat. This loop is faster than disabling seccomp after an incident.
How do you apply seccomp on Docker, Kubernetes, and systemd?
Configuration shape depends on where the process runs. The same JSON profile can often serve Docker Compose and Kubernetes with small packaging changes.
Docker and Docker Compose
services:
laravel-worker:
image: php:8.4-cli
security_opt:
- seccomp:/opt/seccomp/laravel-worker.json
volumes:
- ./seccomp:/opt/seccomp:ro Confirm the filter is active inside the container:
docker exec laravel-worker grep Seccomp /proc/self/status
# Seccomp: 2 means SECCOMP_MODE_FILTER is active Pair seccomp with read-only mounts and CPU and memory limits so a compromised worker cannot exhaust the host. Image scanning with Trivy catches CVEs; seccomp limits what exploited code can do afterward.
systemd for bare-metal PHP-FPM
Many Nepali VPS setups still run PHP-FPM directly on Ubuntu without containers. systemd exposes declarative syscall groups that are easier to maintain than hand-written BPF for simple pools.
# /etc/systemd/system/php8.4-fpm.service.d/seccomp.conf
[Service]
SystemCallFilter=@system-service @file-system @network-io
SystemCallFilter=~@privileged @resources @mount @debug
SystemCallErrorNumber=EPERM Reload and inspect exposure:
sudo systemctl daemon-reload
sudo systemctl restart php8.4-fpm
systemd-analyze security php8.4-fpm.service This approach fits Laravel on Ubuntu with Nginx and mirrors patterns from Ubuntu server hardening guides. For deeper audits, cross-check against CIS benchmark controls.
| Runtime | Config location | Profile format | Verify active |
|---|---|---|---|
| Docker | security_opt: seccomp: | JSON (Moby schema) | /proc/self/status Seccomp: 2 |
| Kubernetes | Pod Security Standard restricted | JSON via runtime default | kubectl describe pod |
| systemd | SystemCallFilter= drop-in | Named syscall sets | systemd-analyze security |
| containerd | OCI runtime seccomp block | JSON (OCI spec) | crictl inspect |
Which syscalls should PHP and Laravel workloads allow or block?
There is no universal list. Extensions, libc versions, and kernel lines all shift requirements. Still, patterns repeat across projects I maintain, including legal-tech portals like Mijar Law Associates and e-commerce stacks on shared EC2 hosts.
Commonly required syscalls
- File I/O:
openat,read,write,close,lseek,fstat,statx,access,getdents64 - Memory:
mmap,munmap,mprotect,brk,madvise - Threads:
clone,clone3,futex,set_robust_list - Network:
socket,connect,accept,sendto,recvfrom,poll,epoll_wait - Time and signals:
clock_gettime,gettimeofday,rt_sigaction,rt_sigreturn
Queue workers that shell out to wkhtmltopdf or ffmpeg need execve and extra pipe syscalls. Redis or MySQL clients may pull in getsockopt and recvmsg. Image libraries sometimes need memfd_create. Trace, do not assume.
Syscalls worth blocking everywhere
ptrace— debug other processes, steal secretsmount,umount2,pivot_root— filesystem escapekexec_load,reboot— kernel or host controlinit_module,finit_module— load kernel modulesunshare,setns— namespace breakout helperbpf— load new BPF programs, including filtersuserfaultfd,perf_event_open— exploit and side-channel primitives
Blocking these aligns with 2026 cybersecurity practice that assumes breach, then contains blast radius. Application fixes still matter. See OWASP Top 10 hardening for Laravel for the layer above seccomp.
What performance impact and operational pitfalls should you expect?
Runtime cost is tiny. BPF filters run in kernel space with fixed instruction counts. Typical web profiles add microseconds per syscall. PHP 8.4 Laravel 12 benchmarks under load rarely move more than one percent once filters are loaded. Memory overhead is shared across threads in the same process.
Operational cost is real. Every new PHP extension, glibc update, or image-processing library can introduce fresh syscalls. Without monitoring, you get mystery 502 responses or queue stalls. Treat profiles as infrastructure code.
- Store profiles in Git next to Docker Compose and systemd units.
- Re-run strace in CI on integration tests; flag unexpected new syscalls.
- Match kernel versions between staging and production. Ubuntu 22.04 and 24.04 differ on
clone3andstatxbehaviour. - Document each allow-list exception with a comment citing the feature that needs it.
- Alert on audit spikes for the
seccomp-denykey. Spikes may mean attack or broken deploy.
Agencies running many small sites can maintain base profiles per stack—Laravel, WordPress, WooCommerce—and overlay project-specific rules. That scales better than one-off heroics per server. Combine with rootless containers, UFW firewall rules, and Ubuntu security updates for a coherent baseline. When you need to validate regex allow lists for log parsers, a regex tester saves time wiring audit dashboards.
Kubernetes clusters should adopt the restricted Pod Security Standard, which enables the runtime default seccomp profile automatically. See Kubernetes pod security and network policies for how seccomp fits beside seccomp-adjacent controls. For local container experiments, start with Dockerizing a Laravel app before layering custom JSON.
Key Takeaways
- seccomp restricts syscalls at the kernel with BPF filters attackers cannot turn off from userspace.
- Trace real staging workloads with
strace; never deploy a guessed allow list. - Extend Docker's default JSON profile, then tighten using audit logs for denied syscalls.
- Apply via Docker
security_opt, Kubernetes restricted PSS, or systemdSystemCallFilter. - Block
ptrace,mount,bpf, and module-loading calls on every web-facing pool. - Version-control profiles, retest on kernel or PHP extension changes, and roll out workers first.
People Also Ask
Does seccomp slow down PHP or Laravel applications?
No meaningful slowdown appears in typical production loads. BPF evaluation adds microseconds per syscall. Once loaded, the filter is shared by all threads in the process. Benchmark before and after if you run extreme syscall-heavy workloads, but standard HTTP and queue traffic rarely notice a difference.
What is the difference between seccomp and AppArmor or SELinux?
seccomp filters syscalls only. AppArmor and SELinux enforce path, capability, and label policies on files and processes. They complement each other. A complete hardening stack uses seccomp plus MAC, dropped capabilities, and namespaces together rather than picking one tool.
Can seccomp block container escape exploits?
It blocks many escape steps that rely on forbidden syscalls such as mount or unshare. It does not fix kernel bugs by itself. Keep kernels patched, run rootless or non-root users, and combine seccomp with read-only root filesystems for practical escape resistance.
Should I use Docker's default seccomp profile or write a custom one?
Start with Docker's default profile for most services. It already denies numerous dangerous syscalls. Move to a custom JSON profile when audit logs show you can remove permissions safely or when compliance requires explicit documentation of every allowed call.
Put seccomp to work on your stack
Pick one non-critical service this week—a Laravel queue worker on staging, a sidecar, or a PHP-FPM pool behind low traffic. Apply Docker's default profile or a systemd SystemCallFilter, enable audit logging, and watch for denials for 48 hours. Expand once the allow list is stable. Sustainable seccomp practice beats a perfect profile that never ships. For hands-on help profiling syscalls on your VPS or container fleet, contact us or message me directly about your environment. If you prefer a full hardening review, see our support and maintenance services and related production Docker guides.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

