The Handbook

    Theme
    • Guides
        • Check a system before installing FreeBSD
        • Orient a Linux administrator on FreeBSD
        • Supported FreeBSD releases
        • Choose a FreeBSD documentation and support channel
        • Move files safely without GNU mv -t
        • Update, upgrade, or update packages?
        • Upgrade 14.4 or 15.0 to FreeBSD 15.1
        • Choose packages, ports, or poudriere
        • Choose the quarterly or latest package branch
        • Choose a FreeBSD download or package mirror
        • Make a system setting persistent
        • Configure locale, keyboard, and time zone
        • Configure a serial console for recovery
        • Choose a custom kernel, module, or loader setting
        • Run a Linux binary with the compatibility layer
        • Check desktop and laptop hardware before installation
        • Choose and check a graphics driver
        • Choose Xorg or Wayland
        • Build a maintainable desktop baseline
        • Install and check a desktop browser
        • Check laptop Wi-Fi, power, and suspend
        • Check multimedia readiness
        • Choose and configure a printing stack
        • Run Windows applications with Wine
        • Snapshot and replicate a ZFS dataset
        • Choose a filesystem and storage layout
        • Operate ZFS without losing the recovery path
        • Restore files from a ZFS snapshot
        • Read ZFS pool health and run a scrub
        • Replace a failed device in a ZFS mirror
        • Replicate a ZFS dataset over SSH
        • Change PF safely on a remote host
        • Configure a narrow WireGuard tunnel
        • Plan a FreeBSD home server
        • Choose a mail server or an outgoing relay
        • Publish a network service safely
        • Choose a jail network model
        • Choose native jails or a jail manager
        • Choose a jail or a bhyve virtual machine
        • Establish a FreeBSD security baseline
        • Choose a MAC policy
        • Audit security-relevant activity
        • Start a DTrace performance investigation
    • Integrations
        • Create a first jail with Bastille
        • Compare jail managers and OCI tooling
        • Publish a Bastille service through PF
        • Mount a ZFS dataset in a Bastille jail
        • Update and upgrade Bastille jails
        • Back up and restore a Bastille jail
        • Prepare bhyve and vm-bhyve
        • Choose NFS or Samba for file sharing
        • Choose ZFS backup automation
        • Operate a signed poudriere repository
        • Manage FreeBSD configuration with Ansible or Salt
        • Run Motion with webcamd on FreeBSD
        • Design a reverse proxy, certificates, and monitoring
    • FAQ
      • Troubleshooting
          • Recover an interrupted freebsd-update run
          • Resolve a package repository or ABI mismatch
          • Diagnose the FreeBSD boot path
          • Recover with a ZFS boot environment
          • Diagnose DNS, routing, and firewall paths
          • Diagnose network mbuf exhaustion
          • Bind a service to a low port without running it as root
          • Diagnose audio output or input
          • Diagnose webcamd, cuse, and a webcam
          • Fix USB device permissions without opening every device
      • About this handbook
      • Synopsis
      • Choose the privilege boundary first
      • Understand the composed policy
      • Inventory the current state
      • Test one qualified rule
      • Make the tested policy persistent
      • Roll back safely
      • Primary references

      Bind a service to a low port without running it as root

      Last reviewed
      13 August 2026
      Applies to
      15.1-RELEASE, 15.0-RELEASE, 14.4-RELEASE

      Synopsis #

      FreeBSD normally reserves low TCP and UDP ports for privileged processes. The mac_portacl(4) policy can authorize a numeric user or group ID to bind one specific port without leaving the complete service running as root.

      This design changes two composed controls. The ordinary reserved-port range must stop denying the bind, and mac_portacl must then enforce an explicit allowlist over the same range. Disabling the reserved range without an active, verified MAC policy removes the intended protection. An application capable of dropping privilege after opening its socket, or PF redirection from a low port to an unprivileged high port, may be a smaller change.

      Choose the privilege boundary first #

      Prefer a service’s documented privilege-separation mode when a small root-owned supervisor opens the socket and the worker processes run under a service account. This keeps lifecycle and socket ownership within the application model.

      PF redirection can keep the application entirely on a high port, but adds translation and firewall state to the packet path. Treat that as a firewall change and follow Change PF safely on a remote host .

      Use mac_portacl when a direct low-port bind by a particular service identity is an explicit requirement. Record that identity, protocol, port, start order, and recovery owner in the security baseline .

      Understand the composed policy #

      mac_portacl controls explicitly bound local TCP and UDP ports up to security.mac.portacl.port_high. It does not govern an automatically selected source port when an application binds port 0.

      Rules have this syntax:

      idtype:id:protocol:port[,idtype:id:protocol:port,...]
      

      idtype is uid or gid; id, port, and group or user identifiers are numeric; and protocol is tcp or udp. Assigning security.mac.portacl.rules replaces the complete rules string, so an existing list must be preserved and reviewed rather than overwritten as if the setting appended one rule.

      The related boundaries are:

      SettingEffect
      security.mac.portacl.enabledEnforces the policy when nonzero; default is enabled after the module loads
      security.mac.portacl.port_highSets the highest port governed by the policy; default is 1023
      security.mac.portacl.suser_exemptAllows root to bind protected ports without an ACL entry when nonzero; default is exempt
      security.mac.portacl.rulesContains the complete numeric TCP and UDP allowlist
      net.inet.ip.portrange.reservedlow and reservedhighSeparately impose the traditional privileged-port restriction

      MAC policies compose with other access checks; an ACL entry cannot override the reserved-port denial. Conversely, releasing the reserved range is safe only while the intended MAC policy covers that range.

      Inventory the current state #

      Before loading or changing the module on a production host, record existing low-port listeners and settings:

      # sockstat -4 -6 -l
      # sysctl net.inet.ip.portrange.reservedlow
      # sysctl net.inet.ip.portrange.reservedhigh
      # kldstat -n mac_portacl.ko
      

      If the module is already loaded, also record its complete state:

      # sysctl security.mac.portacl.enabled
      # sysctl security.mac.portacl.port_high
      # sysctl security.mac.portacl.suser_exempt
      # sysctl security.mac.portacl.rules
      

      The stock FreeBSD kernel includes the MAC framework, and the policy can be loaded as mac_portacl.ko. Loading a policy on a host whose reserved-port range was previously modified can immediately affect non-root binds, so the change belongs in a maintenance window with a console or equivalent recovery path.

      Test one qualified rule #

      The following example assumes a dedicated account named websvc has numeric UID 1001 and must bind TCP port 80. Confirm the actual account rather than assuming its UID:

      $ id -u websvc
      1001
      

      Load the policy, establish its boundary and complete rule list, and only then release the separate reserved range:

      # kldload mac_portacl
      # sysctl security.mac.portacl.enabled=1
      # sysctl security.mac.portacl.port_high=1023
      # sysctl security.mac.portacl.suser_exempt=1
      # sysctl security.mac.portacl.rules=uid:1001:tcp:80
      # sysctl net.inet.ip.portrange.reservedlow=0
      # sysctl net.inet.ip.portrange.reservedhigh=0
      

      This is a syntax example for one new policy, not an instruction to replace an existing ACL. Multiple requirements belong in the one comma-separated rules value. suser_exempt=1 preserves the default root exemption; setting it to zero also subjects root-owned services to the allowlist and can break boot or recovery services that bind low ports.

      Restart only the affected service, then verify the listener’s user, address, protocol, and port:

      # sockstat -4 -6 -l
      

      Also confirm that another unprivileged test identity cannot bind the protected port. A successful intended bind without a negative test does not prove that the allowlist is enforcing the boundary.

      Make the tested policy persistent #

      Load the module during boot with this line in /boot/loader.conf:

      mac_portacl_load="YES"
      

      Place the reviewed runtime values in /etc/sysctl.conf so they are applied before normal services start. For the example policy, the relevant assignments are:

      security.mac.portacl.enabled=1
      security.mac.portacl.port_high=1023
      security.mac.portacl.suser_exempt=1
      security.mac.portacl.rules=uid:1001:tcp:80
      net.inet.ip.portrange.reservedlow=0
      net.inet.ip.portrange.reservedhigh=0
      

      The security.mac.portacl.rules value is a runtime sysctl rather than a loader tunable. Keep the module load and the runtime policy in their respective files. Review Make a system setting persistent before rebooting.

      After reboot, verify every saved value and repeat both the intended-bind and denied-bind tests. A running process is not sufficient if it inherited a socket opened before the policy took effect.

      Roll back safely #

      If the intended or negative test fails, first restore the captured reservedlow and reservedhigh values. That reinstates the prior privileged-port boundary independently of the MAC ACL. Then restore the previous rules, port_high, suser_exempt, and enabled values, and restart the affected service.

      Remove the unaccepted lines from /etc/sysctl.conf and mac_portacl_load from /boot/loader.conf if the policy was newly introduced. A clean reboot during the maintenance window confirms that no unrecorded runtime state remains.

      Do not respond to a lockout by widening the ACL or setting a larger port_high without identifying the failed access check. Restore the known state, then test the reserved-port and MAC layers separately.

      Primary references #

      • FreeBSD Handbook: Mandatory Access Control
      • mac_portacl(4)
      • mac(4)
      • sysctl(8)
      • loader.conf(5)
      • sockstat(1)

      Independent documentation. Not affiliated with or endorsed by the FreeBSD Project or the FreeBSD Foundation.

      Report a bug
      • Synopsis
      • Choose the privilege boundary first
      • Understand the composed policy
      • Inventory the current state
      • Test one qualified rule
      • Make the tested policy persistent
      • Roll back safely
      • Primary references