seccomp: Restrict Syscalls for Security

Khimananda Oli 8 min read Virtualization
seccomp: Restrict Syscalls for Security

By Khimananda Oli | Last reviewed: August 2026

Modern container runtimes expose hundreds of Linux system calls by default, creating a massive attack surface that adversaries exploit for privilege escalation and container escapes. Applying seccomp: Restrict Syscalls for Security is the most effective kernel-level defense to limit this exposure without modifying application code. This guide walks you through building, testing, and enforcing custom seccomp profiles in Docker and Kubernetes environments to achieve genuine least-privilege execution.

How does seccomp: Restrict Syscalls for Security actually work?

Secure Computing Mode (seccomp) is a Linux kernel facility that intercepts every system call a process attempts to make. When a filter is active, the kernel evaluates the call against a Berkeley Packet Filter (BPF) program before execution. If the syscall matches an allowed rule, it proceeds normally; if not, the kernel returns EPERM, kills the process, or logs the violation depending on the configured action. This happens entirely in kernel space with negligible overhead, making it ideal for high-throughput production workloads.

ApplicationUser Spacesyscall()Seccomp BPF FilterKernel InterceptionEvaluate RulesALLOWBLOCKLinux KernelSyscall TableExecute / Denyseccomp: Restrict Syscalls for Security enforces policy before kernel execution
Seccomp intercepts syscalls at the kernel boundary, allowing only explicitly permitted operations

In practice, you rarely write raw BPF assembly. The OCI Runtime Specification defines a JSON schema that container runtimes like runc and crun translate into BPF automatically. This abstraction lets you declare allowed syscalls, architectures, and actions declaratively. For teams managing Ubuntu server hardening, seccomp complements AppArmor and UFW by operating at a lower abstraction layer—directly controlling what the process can ask the kernel to do.

A common mistake is assuming seccomp replaces other isolation mechanisms. It does not. Seccomp filters syscalls but cannot restrict file paths, network sockets, or capabilities. Use it alongside namespace isolation, capability dropping, and mandatory access control for defense-in-depth. When auditing for SOC 2 or ISO 27001, seccomp provides concrete evidence of least-privilege enforcement at the kernel level, which auditors value highly because it is tamper-resistant and measurable.

How do you create and test custom seccomp profiles?

Start with the default Docker or Kubernetes profile as your baseline. These block approximately 44 dangerous syscalls like unshare, mount, and kexec_load while allowing everything else. For most web applications, the default is sufficient. Custom profiles become necessary when running untrusted code, multi-tenant workloads, or when compliance demands explicit allowlisting.

Generate a baseline profile from actual usage

Rather than guessing which syscalls your application needs, observe it under realistic load. Use strace to capture syscall activity during integration tests or staging traffic:

<!-- Generate syscall list from application execution -->
strace -c -o syscall_stats.txt ./your-application --test-mode
sort -t= -k2 -nr syscall_stats.txt | head -30

<!-- Or capture unique syscalls for profile generation -->
strace -e trace=all -f -o raw_trace.txt ./your-application
grep -oP '^[a-z_]+' raw_trace.txt | sort -u > allowed_syscalls.txt

Convert this list into an OCI-compliant JSON profile. The structure requires specifying the default action and explicit allowances:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "archMap": [
    {
      "architecture": "SCMP_ARCH_X86_64",
      "subArchitectures": ["SCMP_ARCH_X86"]
    }
  ],
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "stat", "fstat"],
      "action": "SCMP_ACT_ALLOW"
    },
    {
      "names": ["socket", "connect", "bind", "listen", "accept"],
      "action": "SCMP_ACT_ALLOW",
      "comment": "Network operations for HTTP server"
    }
  ]
}

Test safely with logging before enforcement

Never deploy a restrictive profile directly to production. First, set the default action to SCMP_ACT_LOG (kernel ≥5.11) or SCMP_ACT_TRACE to record violations without breaking the application. Monitor logs with journalctl or your structured logging pipeline to identify missing syscalls. Iterate until the application runs cleanly for 24–48 hours under production-like load, then switch to SCMP_ACT_ERRNO.

  • Always include architecture specifications—x86_64 and arm64 have different syscall numbers
  • Group related syscalls with comments for auditability and maintenance
  • Version-control profiles alongside application code in the same repository
  • Automate profile validation in CI using runc spec or crun validate

How do you enforce seccomp profiles in Kubernetes and Docker?

Enforcement mechanisms differ between standalone Docker and orchestrated Kubernetes environments. Both support the OCI JSON format, but the integration points vary significantly.

Docker and containerd integration

Apply profiles per-container using the --security-opt flag. Store profiles in a known directory like /etc/docker/seccomp/ for consistency across hosts:

<!-- Run container with custom seccomp profile -->
docker run --rm \
  --security-opt seccomp=/etc/docker/seccomp/webapp.json \
  --cap-drop ALL \
  myapp:latest

<!-- Verify active profile inside container -->
docker exec <container_id> cat /proc/self/status | grep Seccomp
<!-- Output: Seccomp: 2 (filter mode active) -->

For containerd (used by Kubernetes), configure the default profile globally in /etc/containerd/config.toml to ensure all pods inherit baseline restrictions even if developers forget to specify one.

Kubernetes Pod Security Standards and annotations

Kubernetes 1.25+ integrates seccomp through Pod Security Standards (PSS). Set the seccompProfile.type to RuntimeDefault or Localhost in your pod spec. For custom profiles, place JSON files on each node at /var/lib/kubelet/seccomp/profiles/ and reference them by filename:

apiVersion: v1
kind: Pod
metadata:
  name: secure-webapp
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/webapp-custom.json
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
DeveloperGenerate Profile(strace + test)Git Repositoryprofiles/webapp.jsonVersion ControlledCI PipelineValidate + Testin StagingNode DistributionAnsible / DaemonSet/var/lib/kubelet/Kubernetes ClusterPod SpecseccompProfileContainer RuntimeEnforce BPFEnd-to-end workflow for seccomp: Restrict Syscalls for Security in Kubernetes
Distribute seccomp profiles through GitOps and validate in staging before production enforcement

For teams using GitOps with ArgoCD, store profiles in the same repository as your manifests and use a DaemonSet or Ansible playbook to sync them to nodes. This ensures profile versions match application deployments atomically. Never manually copy profiles to nodes—this breaks reproducibility and audit trails.

What are the trade-offs between seccomp, AppArmor, and capabilities?

Understanding where seccomp fits in the security stack prevents misapplication. Each mechanism operates at a different layer and solves distinct problems. Combining them correctly yields compounding security benefits; using the wrong tool creates gaps or operational friction.

MechanismScopeGranularityBest ForLimitations
seccompSystem callsPer-syscall, argument-awareBlocking kernel exploits, container escape preventionCannot filter file paths or network addresses
AppArmor/SELinuxResources (files, sockets)Path-based, label-basedRestricting file access, IPC, device accessComplex policy authoring, distro-specific
CapabilitiesRoot privileges38 discrete privilege bitsDropping specific root powers (NET_RAW, SYS_ADMIN)Coarse-grained, binary on/off
NamespacesResource visibilityPID, network, mount, userProcess and network isolationNo syscall filtering, bypassable with CAP_SYS_ADMIN

In my experience securing fintech infrastructure for Nepal-based companies handling sensitive payment data, the winning combination is: drop all capabilities except those strictly required, apply a restrictive seccomp profile to block unused syscalls, and use AppArmor for path-based file restrictions. This layered approach satisfies both PCI-DSS and local regulatory requirements while keeping the operational burden manageable.

Application CodeBusiness Logic, Dependencies, User InputCapabilities + NamespacesDrop Privileges, Isolate Resourcesseccomp: Restrict Syscalls for SecurityKernel-Level Syscall Filtering (BPF)Linux KernelHardware Abstraction, System Call TableDefense-in-depth: each layer compensates for gaps in others
Seccomp sits between capability restrictions and the kernel, providing the last line of defense before syscall execution

A frequent pitfall is over-restricting seccomp and causing silent failures in background threads or error-handling paths. Applications often use syscalls like epoll_create1 or getrandom only during initialization or failure recovery. Your testing must cover these edge cases comprehensively. When debugging crashes after enabling seccomp, check dmesg for audit messages showing blocked syscalls—these include the exact syscall number and arguments, making profile refinement straightforward.

Implementing seccomp: Restrict Syscalls for Security in Production

Adopting seccomp: Restrict Syscalls for Security transforms your container security posture from passive trust to active enforcement. Start with runtime defaults, measure actual syscall usage in staging, and progressively tighten profiles based on observed behavior. Integrate profile validation into your CI pipeline alongside container image scanning to catch regressions before deployment. Remember that security controls only deliver value when they are maintained—schedule quarterly reviews of seccomp profiles as dependencies and application features evolve. If your team needs help designing or auditing syscall filters for compliance-critical workloads, reach out to discuss your specific requirements.

Frequently Asked Questions

Seccomp filters system calls at the kernel level, blocking unauthorized operations. Restricting syscalls reduces attack surface by preventing exploits from executing dangerous kernel functions like ptrace or mount in compromised containers or applications.

Use the security-opt flag with a custom JSON profile path when running containers. Docker applies a default restrictive profile automatically, but production workloads require tailored profiles generated via audit logs to avoid breaking legitimate application functionality.

Yes, overly restrictive profiles block required syscalls causing crashes or silent failures. Always test in staging first using SCMP_ACT_LOG mode to record violations without enforcement, then refine the allowlist before switching to SCMP_ACT_ERRNO in production.

Tools like Inspektor Gadget, oci-seccomp-bpf-hook, and strace analyze runtime syscall patterns to create baseline profiles. These capture actual application behavior during testing, producing accurate allowlists that minimize manual guesswork and reduce false positives during enforcement.

They complement each other. Seccomp filters specific syscalls at the kernel entry point while AppArmor enforces file and capability policies. Using both provides defense-in-depth, as seccomp blocks dangerous operations even if AppArmor policy gaps exist.

Minimal overhead occurs because BPF filters execute in kernel space before syscall processing. Complex profiles with hundreds of rules add microseconds per call. Performance impact is negligible compared to network or disk latency in typical web applications.

Check dmesg or auditd logs for seccomp violation messages containing the blocked syscall number and process ID. Map numbers to names using ausyscall, then update your profile to permit legitimate calls or fix the application code triggering unexpected behavior.

Strict mode only allows read, write, exit, and sigreturn syscalls, suitable for simple sandboxed tasks. Filter mode uses BPF programs to define granular allowlists with argument inspection, enabling complex applications to run safely with precise syscall restrictions.

Yes, wrap PHP-FPM or CLI workers with systemd SystemCallFilter directives or use libseccomp bindings directly in C extensions. This hardens bare-metal Laravel deployments against kernel exploitation without requiring containerization or orchestration platform changes.

Use jq to check JSON structure and validate against the OCI runtime spec schema. Test loading with runc or crun in dry-run mode before deployment. Invalid profiles cause container startup failures, so automated validation in CI pipelines prevents runtime incidents.

Block mount, umount, reboot, swapon, kexec_load, and ptrace unless explicitly required. These enable privilege escalation or host compromise. Also restrict unshare and clone flags that create new namespaces, preventing container escape attempts through namespace manipulation.

Yes, Kubernetes 1.30+ supports SeccompProfile in pod security contexts referencing node-local or ConfigMap-stored profiles. The seccomp-operator automates profile distribution across nodes, eliminating manual file placement and ensuring consistent enforcement across cluster scaling events.

Direct bypass is difficult since filtering occurs in kernel space before execution. However, allowed syscalls with vulnerable argument handling can still be exploited. Combine seccomp with capabilities dropping, read-only filesystems, and network policies for comprehensive defense.

Review profiles quarterly or after major dependency upgrades. New library versions may introduce previously unused syscalls. Automated profiling in CI/CD pipelines catches regressions early, while periodic audits ensure profiles remain aligned with evolving application requirements and threat landscapes.

Docker and containerd ship default profiles covering standard Linux utilities. Community repositories like seccomp-profiles provide baselines for Nginx, PostgreSQL, and PHP-FPM. Fork these as starting points, then customize based on your specific workload testing rather than using generic profiles unchanged.