Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

seccomp: Restrict Syscalls for Security

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.

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.

PHP-FPM WorkerApplication CodeBPF Filter ProgramSyscall WhitelistLinux KernelExecute / KillAllowed: read, write, openat, futex→ Kernel executes normallyBlocked: ptrace, mount, kexec→ SIGSYS terminationEvery syscall evaluated before kernel entryZero runtime overhead after initial load
Seccomp architecture: BPF filter intercepts every syscall between application and kernel, enforcing whitelist policy

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.

1. Tracestrace -f appCollect syscalls2. GenerateJSON profileWhitelist + ERRNO3. TestStaging deployMonitor SIGSYS4. DeployProduction rolloutAudit logging ONCritical: Exercise ALL code paths during tracingError handlers • File uploads • DB reconnects • Cache misses • External APIs❌ Never deploy untested profilesCauses silent failures in production✓ Always keep unrestricted fallbackEnables instant rollback on issues
Safe seccomp profile development: trace comprehensively, generate automatically, test thoroughly, deploy cautiously

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.

RuntimeConfiguration MethodProfile FormatReload CommandVerification
Dockersecurity_opt: seccomp:JSON (Docker schema)docker compose up -d/proc/self/status
KubernetesPod Security Standard / AnnotationJSON (ConfigMap)kubectl applykubectl describe pod
systemd (PHP-FPM)SystemCallFilter=Declarative groupssystemctl restartsystemd-analyze security
Containerd/CRI-ORuntime annotationJSON (OCI spec)crictl stop/startcrictl 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 replacement
  • mount, umount2, pivot_root — Filesystem manipulation
  • reboot, swapon, swapoff — System control
  • init_module, finit_module, delete_module — Kernel module loading
  • bpf — 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.

WITHOUT seccompAttacker exploits PHP vulnerabilityCalls ptrace → dumps credentialsCalls mount → escapes containerFull host compromiseWITH seccompSame PHP vulnerability exploitedptrace → SIGSYS (blocked)mount → SIGSYS (blocked)Attack chain terminatedKey Insight: seccomp breaks exploit chainsEven successful code execution cannot access restricted kernel interfacesDefence Depth Layer 1Input validation / WAFDefence Depth Layer 2seccomp syscall filterDefence Depth Layer 3Namespaces / Capabilities
Attack surface comparison: seccomp prevents post-exploitation escalation even when application vulnerabilities exist

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:

  1. Version-control profiles alongside application code in Git. Treat them as infrastructure-as-code.
  2. Automate syscall discovery in CI pipelines. Run strace during integration tests and fail builds if new syscalls appear unexpectedly.
  3. Maintain environment parity. Staging must mirror production kernel version and PHP extensions. Syscall requirements differ between kernel 5.15 and 6.8.
  4. Document exceptions. When you add a syscall to the whitelist, record why in a comment. Future maintainers need context.
  5. 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.

Frequently Asked Questions

Seccomp filters system calls at the kernel level, restricting processes to only allowed operations. For PHP-FPM or Nginx, this prevents exploited code from executing dangerous syscalls like execve or ptrace, limiting damage even if application-level defenses fail.

Seccomp operates at the syscall interface with minimal overhead, while AppArmor and SELinux enforce path-based or label-based access control at higher abstraction levels. Seccomp complements rather than replaces them; I often layer seccomp profiles inside AppArmor-confined PHP-FPM workers for defense in depth on Ubuntu 24.04 servers.

Start with libseccomp’s default allowlist plus read, write, openat, close, fstat, lseek, mmap, mprotect, brk, rt_sigaction, epoll_wait, accept4, sendto, recvfrom, and clone3. Block execve, ptrace, mount, reboot, and kexec_load. Test thoroughly in log-only mode before enforcing, as missing syscalls cause silent worker crashes.

Yes, especially if workers spawn subprocesses or use pcntl_fork. Artisan commands running via cron may need broader profiles than HTTP workers. In production Laravel deployments, I maintain separate seccomp profiles for PHP-FPM, queue:work processes, and scheduler daemons to avoid false positives during peak traffic or batch jobs.

Use strace -f -o trace.log to capture syscalls during representative load, then convert with tools like scmp_sys_resolver or oci-seccomp-bpf-hook. For PHP-FPM, run strace against multiple request types including file uploads, API calls, and cache misses. Always validate generated profiles in audit mode before enforcement to catch edge cases missed during tracing.

Negligible in practice. BPF filtering adds microseconds per syscall, typically under 1% overhead for PHP-FPM. The real cost is development time for profile tuning. On high-traffic WooCommerce sites I’ve secured, seccomp added no measurable latency while blocking exploit attempts targeting imagemagick and GD libraries.

Store JSON profiles in your repository under deploy/seccomp/. Add a task to copy profiles to /etc/seccomp/ and set permissions 644 root:root. Hook into deploy:update_code to symlink versioned profiles, then reload PHP-FPM via systemctl reload php8.4-fpm. Never store profiles in shared storage since they’re version-controlled security artifacts.

No, Docker applies its own default seccomp profile unless overridden. Pass custom profiles via --security-opt seccomp=/path/to/profile.json in docker run or compose files. For Laravel containerized deployments, I extend Docker’s default profile rather than replacing it, adding PHP-specific allowances while retaining container isolation guarantees.

Check dmesg or journalctl for SECCOMP audit messages showing blocked syscall numbers and PIDs. Use scmp_sys_resolver -r to translate to names. Enable audit logging with auditd rules for finer detail. Temporarily switch to SCMP_ACT_LOG instead of SCMP_ACT_ERRNO to observe violations without killing processes during staging validation.

Rarely. Shared hosts restrict kernel access and rarely expose seccomp configuration. Focus on application hardening instead. For VPS or dedicated servers running legal-tech portals or eCommerce platforms where you control the OS, seccomp provides meaningful attack surface reduction that justifies the initial profiling effort.

Enabling enforcement without sufficient logging, copying profiles between different PHP versions without revalidation, forgetting architecture-specific syscalls on ARM servers, and assuming dev environment parity with production. Always test on identical OS and PHP builds, start with log-only mode, and maintain rollback procedures for immediate profile disablement.

Database and Redis clients use socket, connect, sendto, and recvfrom syscalls that must be explicitly allowed. Connection pooling via persistent connections reduces syscall frequency. When securing Laravel apps connecting to MySQL 8.4 and Redis 7.x, verify TLS handshake syscalls like getrandom and futex aren’t blocked, as failures manifest as intermittent connection timeouts.

Partially. Seccomp blocks malicious post-install scripts attempting execve or network exfiltration during composer install. However, it cannot stop logic bombs executing within allowed PHP runtime behavior. Combine seccomp with composer audit, locked dependencies, and CI pipeline scanning. Run composer install only in build stages, never on production servers directly.

Initial profiling and testing takes 8–16 hours for experienced engineers, roughly NPR 40,000–80,000 (USD 300–600) at Nepal senior developer rates. Ongoing maintenance adds 2–4 hours per major PHP or framework upgrade. Budget-conscious clients should prioritize seccomp after basic hardening like firewall rules, updated packages, and least-privilege file permissions.

Skip seccomp for short-lived scripts, development environments, or systems where debugging time exceeds security benefit. For low-risk brochure sites with no user input or payments, standard OS hardening suffices. Reserve seccomp for production systems handling sensitive data, financial transactions, or legal documents where breach consequences justify operational complexity.

Share this article

Quick Contact Options
Choose how you want to connect me: