
August 22, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
If you run PHP applications or containerised services on Linux, understanding seccomp: Restrict Syscalls for Security is one of the highest-impact hardening steps available today. Most production breaches exploit kernel interfaces that your application never actually needs, yet remain exposed by default. In my experience maintaining secure web servers in Nepal and globally, applying a tailored seccomp profile reduces attack surface without degrading performance or requiring code changes.
--security-opt, systemd SystemCallFilter, or PHP-FPM pool config to prevent exploits from accessing unused kernel functionality.What exactly does seccomp: Restrict Syscalls for Security do in production?
Seccomp (Secure Computing Mode) operates as a kernel-level firewall for system calls. When a process invokes a syscall not present in its allowed list, the kernel immediately terminates it with SIGSYS before any execution occurs. This differs fundamentally from userspace security tools because the restriction cannot be bypassed by application code, library injection, or memory corruption exploits.
The mechanism relies on Berkeley Packet Filter (BPF) programs attached to each thread. These tiny programs evaluate every syscall number against a predefined policy. Modern Linux kernels (5.x+) support SECCOMP_MODE_FILTER, which allows complex logic including argument inspection. For web applications running on Ubuntu 22.04 or 24.04, this means you can permit read, write, openat, and futex while permanently blocking dangerous calls like ptrace, kexec_load, or mount.
In practice, I apply seccomp profiles to Laravel queue workers, PHP-FPM pools serving legal-tech portals, and Node.js API gateways. The protection is transparent to application logic but devastating to attackers who rely on chaining syscalls for privilege escalation or container escape. For teams managing Laravel applications in production, this single configuration change often provides more defence than multiple application-layer security packages.
How do you create and test a custom seccomp profile safely?
Creating a working seccomp profile requires discovering which syscalls your application actually uses. Guessing leads to crashes; logging reveals truth. The standard workflow uses strace to capture syscalls during representative workload execution, then converts that list into a BPF filter.
Capture syscalls with strace
<!-- Run your application under strace to collect syscall names -->
sudo strace -f -e trace=all -o /tmp/syscalls.log php artisan queue:work --once
<!-- Extract unique syscall names, sorted -->
grep -oP '(?<=^[0-9]+ )[a-z_]+' /tmp/syscalls.log | sort -u > /tmp/allowed_syscalls.txt
<!-- Count total unique syscalls (typical Laravel worker: 40-70) -->
wc -l /tmp/allowed_syscalls.txt This produces a baseline list. However, strace captures only what happens during that specific execution path. You must exercise error handling, file uploads, database queries, cache operations, and external API calls to avoid missing rare but necessary syscalls. On client projects, I typically run strace across staging traffic for 24 hours before finalising profiles.
Convert to Docker-compatible JSON
Docker expects seccomp profiles in a specific JSON format. Manual conversion is error-prone; use established tooling:
<!-- Generate profile using runc's seccomp tool or docker-seccomp-profile -->
npx @docker/seccomp-profile-generator \
--input /tmp/allowed_syscalls.txt \
--output laravel-worker.json \
--default-action SCMP_ACT_ERRNO
<!-- Validate JSON syntax before deployment -->
jq . laravel-worker.json > /dev/null && echo "Valid JSON" The generated profile sets SCMP_ACT_ERRNO as default action, meaning any syscall not explicitly listed returns EPERM. Some applications handle EPERM gracefully; others crash. Testing in non-production environments prevents outages. Always keep an unrestricted fallback container available during initial rollout.
Debug blocked syscalls without guessing
When a profile blocks something legitimate, you need immediate visibility. Enable audit logging for seccomp violations:
<!-- Add to /etc/audit/rules.d/seccomp.rules -->
-a always,exit -F arch=b64 -F syscall=all -F success=0 -k seccomp-blocked
<!-- Reload audit rules -->
sudo augenrules --load
<!-- Watch violations in real time -->
sudo ausearch -k seccomp-blocked --interpret | tail -f Audit logs show the exact syscall number, process name, and PID. Cross-reference with ausyscall --dump to convert numbers back to names. This feedback loop lets you iteratively refine profiles without disrupting users. On high-traffic e-commerce systems, I've resolved missing syscalls within minutes using this approach rather than disabling seccomp entirely.
How do you apply seccomp profiles to Docker and PHP-FPM?
Deployment method depends on your runtime environment. Docker containers, systemd services, and bare PHP-FPM pools each have distinct configuration mechanisms.
Docker container integration
Docker natively supports seccomp via security options. Place your JSON profile alongside docker-compose.yml:
<!-- docker-compose.yml excerpt -->
services:
laravel-worker:
image: php:8.4-fpm
security_opt:
- seccomp:/opt/seccomp/laravel-worker.json
volumes:
- ./seccomp:/opt/seccomp:ro
restart: unless-stopped For Kubernetes, embed the profile in a ConfigMap and reference it via PodSecurityPolicy or Pod Security Standards (restricted profile in 2026). Containerd and CRI-O follow similar patterns but require runtime-specific annotation keys. Always verify the profile loaded correctly:
<!-- Confirm seccomp is active inside container -->
docker exec laravel-worker cat /proc/self/status | grep Seccomp
<!-- Expected output: Seccomp: 2 (filter mode active) --> PHP-FPM pool configuration
When running PHP-FPM directly on Ubuntu without containers, use systemd's SystemCallFilter directive in the service unit override:
<!-- /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 The @system-service group includes ~60 syscalls typical for daemons. The tilde (~) prefix denies listed groups. This declarative syntax is safer than manual enumeration because systemd maintains curated groups across releases. After editing, run systemctl daemon-reload && systemctl restart php8.4-fpm.
| Runtime | Configuration Method | Profile Format | Reload Command | Verification |
|---|---|---|---|---|
| Docker | security_opt: seccomp: | JSON (Docker schema) | docker compose up -d | /proc/self/status |
| Kubernetes | Pod Security Standard / Annotation | JSON (ConfigMap) | kubectl apply | kubectl describe pod |
| systemd (PHP-FPM) | SystemCallFilter= | Declarative groups | systemctl restart | systemd-analyze security |
| Containerd/CRI-O | Runtime annotation | JSON (OCI spec) | crictl stop/start | crictl inspect |
Which syscalls should you allow for Laravel and PHP applications?
There is no universal safe list—every application differs. However, common patterns emerge across Laravel, Symfony, and WordPress deployments based on years of production observation.
Core PHP-FPM / Laravel baseline
- File I/O:
openat,read,write,close,lseek,fstat,getdents64,access,statx - Memory:
mmap,munmap,mprotect,brk,madvise - Process:
clone,fork,execve(only if spawning subprocesses),wait4,exit_group - Networking:
socket,connect,accept,sendto,recvfrom,setsockopt,getpeername - Synchronisation:
futex,epoll_wait,poll,ppoll - Time:
clock_gettime,clock_nanosleep,gettimeofday - Signals:
rt_sigaction,rt_sigprocmask,rt_sigreturn,sigaltstack
This covers ~95% of typical Laravel request cycles. Queue workers additionally need inotify_add_watch for file monitoring and prctl for process naming. Database-heavy workloads may require io_uring_enter on newer kernels, though most PHP installations still use traditional synchronous I/O.
Syscalls to always block
Certain syscalls have no legitimate use in web application contexts and represent severe escalation vectors:
ptrace— Process debugging/tracing (enables credential theft)kexec_load,kexec_file_load— Kernel replacementmount,umount2,pivot_root— Filesystem manipulationreboot,swapon,swapoff— System controlinit_module,finit_module,delete_module— Kernel module loadingbpf— BPF program loading (recursive seccomp bypass risk)userfaultfd— Userspace page fault handling (exploit primitive)perf_event_open— Performance monitoring side channels
Blocking these eliminates entire classes of container escape and privilege escalation vulnerabilities. Even if an attacker achieves arbitrary code execution through a PHP deserialization flaw or SQL injection, they cannot escalate beyond the seccomp sandbox. This defence-in-depth approach complements modern cybersecurity practices that assume breach will eventually occur.
What are the performance costs and operational trade-offs?
Seccomp imposes negligible runtime overhead. BPF filters execute in kernel space with O(n) complexity where n is filter length. Typical profiles contain 50–100 instructions, adding microseconds per syscall. Benchmarks on PHP 8.4 with Laravel 12 show less than 1% throughput difference under load testing. Memory impact is similarly trivial—filters reside in kernel memory shared across threads.
The real cost is operational complexity. Every dependency update, PHP extension installation, or new feature may introduce previously unseen syscalls. Without proper monitoring, this manifests as mysterious 500 errors or silent job failures. Establish these practices:
- Version-control profiles alongside application code in Git. Treat them as infrastructure-as-code.
- Automate syscall discovery in CI pipelines. Run strace during integration tests and fail builds if new syscalls appear unexpectedly.
- Maintain environment parity. Staging must mirror production kernel version and PHP extensions. Syscall requirements differ between kernel 5.15 and 6.8.
- Document exceptions. When you add a syscall to the whitelist, record why in a comment. Future maintainers need context.
- Monitor violation rates. Sudden spikes indicate either an attack attempt or a broken deployment. Alert on both.
For agencies managing dozens of client sites, consider maintaining base profiles per framework (Laravel, WordPress, Magento) with project-specific overlays. This balances security with maintenance burden. The initial investment pays dividends during security audits and incident response—demonstrating proactive syscall restriction satisfies compliance requirements that application-layer controls alone cannot meet.
Implementing seccomp: Restrict Syscalls for Security Today
Start small. Pick one non-critical service—a background queue worker or staging environment—and apply a conservative profile this week. Monitor for 48 hours, refine based on audit logs, then expand. The goal isn't perfection on day one; it's establishing the feedback loop that makes seccomp: Restrict Syscalls for Security sustainable long-term. If you're managing production PHP infrastructure and need hands-on assistance designing or debugging seccomp policies, reach out directly to discuss your specific environment.

