« Back to Hardening profiles for systemd

Nginx hardening profile

This article has last been updated at .

Introduction

This is a hardening profile to help securing nginx by using systemd unit configuration. It’s goal is to restrict what nginx can do and make it harder for any possible vulnerability to be misused.

The rationale for the selected settings is based on the analysis as part of the article Hardening nginx with systemd security features.

Relevant FAQ: How to use systemctl edit to change a service?

Notes

Depending on external components such as PHP, temporary files may need to be made accessible. When possible, define an alternative directory and keep PrivateTmp enabled.

Changes

  • Add PrivateTmp=yes
  • Changed LockPersonality from ’true’ to ‘yes’
  • Additional comments
  • Add capability: CAP_SETPCAP due to usage of SecureBits
  • Add SecureBits
  • Allow real-time scheduling

Hardening profile

Important notes

  • Test this profile first on a non-production system
  • Reload and restart nginx a few times after applying the new settings
  • Confirm that there is a master process and workers

Excluded settings for this profile

Some settings were considered too strict, too specific, or not compatible for this service unit. For this reason they are not applied to the service.

SettingReason
IPAccountingIP accounting tracks traffic (packets and bytes) to and from the service. It strongly depends on the use-case if this setting is useful for this service.
PrivateNetworkThis service requires networking connectivity
PrivatePIDsThis service is defined with Type=forking and therefore can't be used
ReadOnlyPathsProtectSystem is used, which already marks most of the file system read-only
RestrictRealtimeRealtime scheduling is used within nginx

Settings to consider

There are some settings that can be considered adding. These might not be added yet as they require additional testing. Want to help improving this profile? Test the setting and let it know!

SettingReason or description
RemoveIPCdefines if System V and POSIX IPC objects by the user and group are removed upon stopping the service
PrivateUsersDefine a new user namespace for the process and its children
KeyringModecontrols kernel session keyring and define what is available to the service
PrivateMountsprovides a separated mount namespace to the service
RuntimeDirectoryModeset the default file permissions for runtime directory, which is defined as RuntimeDirectory
DeviceAllowAllow access to a device
DevicePolicydefine level of access to devices in /dev

Applying this profile

Open the service unit for nginx with the editor function.

systemctl edit nginx.service

Copy the following block into the editor. As the profile can change over time, it is suggested to include the link and version. This way changes can easily be compared in the future. Also add your own customizations at the top to simplify the comparison.

################################################################################
# Source: https://linux-audit.com/systemd/hardening-profiles/nginx/
# Profile version: 0.4 [2025-01-06]
################################################################################
# Customizations:
# - Insert here the changes you made to the profile
################################################################################

[Service]
# ===============================
# Paths
# ===============================

# Deny access to /dev/shm directory, suggested when using MemoryDenyWriteExecute=yes
# Details: https://linux-audit.com/systemd/settings/units/inaccessiblepaths/
InaccessiblePaths=/dev/shm

# Do not allow execution of files, except nginx itself
# Details: https://linux-audit.com/systemd/settings/units/noexecpaths/
NoExecPaths=/

# Details: https://linux-audit.com/systemd/settings/units/execpaths/
ExecPaths=/usr/sbin/nginx /usr/lib

# Allow creation of PID file and writing to log files (access|error).log
# Details: https://linux-audit.com/systemd/settings/units/readwritepaths/
ReadWritePaths=/run /var/log/nginx


# ===============================
# Capabilities and system calls
# ===============================

# Only allow: bind to ports < 1024, change file permissions/ownership so nginx workers can use them
# Details: https://linux-audit.com/systemd/settings/units/capabilityboundingset/
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_DAC_OVERRIDE CAP_SETGID CAP_SETPCAP CAP_SETUID

# Only allow the usage of system calls for own hardware platform. Advised when using RestrictAddressFamilies
# Details: https://linux-audit.com/systemd/settings/units/systemcallarchitectures/
SystemCallArchitectures=native

# Define an allow-list filter for system calls
# Details: https://linux-audit.com/systemd/settings/units/systemcallfilter/
SystemCallFilter=@system-service

# Explicit block memfd_create() due to using MemoryDenyWriteExecute=yes
SystemCallFilter=~memfd_create

# Block @mount, suggested as extension to PrivateTmp=, PrivateDevices=, ProtectSystem=, ProtectHome=, ProtectKernelTunables=, ProtectControlGroups=, ProtectKernelLogs=, ProtectClock=, ReadOnlyPaths=, InaccessiblePaths= and ReadWritePaths=.
SystemCallFilter=~@clock @mount @reboot

# Set securebits flags of prctl(2) PR_SET_SECUREBITS/PR_GET_SECUREBITS operations. Nginx needs keep-caps to work, also using noroot won't work.
# Details: https://linux-audit.com/systemd/settings/units/securebits/
SecureBits=keep-caps-locked no-setuid-fixup no-setuid-fixup-locked noroot-locked


# ===============================
# Memory
# ===============================

# Do not allow memory segments to be writable+executable
# Details: https://linux-audit.com/systemd/settings/units/memorydenywriteexecute/
MemoryDenyWriteExecute=yes


# ===============================
# Privileges
# ===============================

# Do not allow to gain new privileges
# Details: https://linux-audit.com/systemd/settings/units/nonewprivileges/
NoNewPrivileges=yes


# ===============================
# Personality
# ===============================

# Do not allow personality switch
# Details: https://linux-audit.com/systemd/settings/units/lockpersonality/
LockPersonality=yes


# ===============================
# Private
# ===============================

# Restrict access to devices in /dev, except for pseudo-devices like /dev/null, /dev/random, /dev/zero
# Details: https://linux-audit.com/systemd/settings/units/privatedevices/
PrivateDevices=yes

# Limit access to temporary directories such as /tmp and /var/tmp by creating a new namespace
# Details: https://linux-audit.com/systemd/settings/units/privatetmp/
PrivateTmp=yes


# ===============================
# Protect and Proc
# ===============================

# Do not allow changing the system clock
# Details: https://linux-audit.com/systemd/settings/units/protectclock/
ProtectClock=yes

# Details: https://linux-audit.com/systemd/settings/units/protectcontrolgroups/
ProtectControlGroups=yes

# No access to home directories
# Details: https://linux-audit.com/systemd/settings/units/protecthome/
ProtectHome=yes

# Do not allow changing the hostname
# Details: https://linux-audit.com/systemd/settings/units/protecthostname/
ProtectHostname=yes

# No access to kernel log ring buffer
# Details: https://linux-audit.com/systemd/settings/units/protectkernellogs/
ProtectKernelLogs=yes

# Do not allow loading kernel modules
# Details: https://linux-audit.com/systemd/settings/units/protectkernelmodules/
ProtectKernelModules=yes

# Don't allow changes to sysctl settings
# Details: https://linux-audit.com/systemd/settings/units/protectkerneltunables/
ProtectKernelTunables=yes

# No access to information about other users in /proc
# Details: https://linux-audit.com/systemd/settings/units/protectproc/
ProtectProc=invisible

# Most of the system paths will no longer be writable
# Details: https://linux-audit.com/systemd/settings/units/protectsystem/
ProtectSystem=strict

# Restricts information from /proc that not directly associated with process management and introspection
# Details: https://linux-audit.com/systemd/settings/units/procsubset/
ProcSubset=pid


# ===============================
# Restrictions
# ===============================

# Limit address families INET/INET6 for IPv4/IPv6, UNIX for local functions such as syslog
# Details: https://linux-audit.com/systemd/settings/units/restrictaddressfamilies/
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# No access to Linux namespace functionality
# Details: https://linux-audit.com/systemd/settings/units/restrictnamespaces/
RestrictNamespaces=yes

# Do not allow setting set-user-ID and set-group-ID
# Details: https://linux-audit.com/systemd/settings/units/restrictsuidsgid/
RestrictSUIDSGID=yes


# ===============================
# Sockets
# ===============================

# Block socket bind as the default, then use SocketBindAllow to create allow-list
# Details: https://linux-audit.com/systemd/settings/units/socketbinddeny/
SocketBindDeny=any

# Allow port 80 (HTTP)
# Details: https://linux-audit.com/systemd/settings/units/socketbindallow/
SocketBindAllow=tcp:80

# Allow port 443 (HTTPS)
SocketBindAllow=tcp:443

# Allow port 443 (HTTPS), HTTP/3 with QUIC
SocketBindAllow=udp:443


# ===============================
# Umask
# ===============================

# Create new directories and files with 700/600 file permissions
# Details: https://linux-audit.com/systemd/settings/units/umask/
UMask=0077

Restart the nginx service

systemctl restart nginx.service

After restarting the service, check the status.

systemctl status nginx.service

Last step is checking the service and its log files.

Troubleshooting

All is working? Perfect!

  • A file mentioned in the hardening profile does not exist on your system
    • Adjust the path
    • Add a hyphen before optional paths)
  • Process can't start as it requires additional paths
    • Add the paths
    • Make sure that paths that needs to be written to are defined in ReadWritePaths
  • Process can't bind to a port
    • Specify port with SocketBindAllow

See troubleshooting a failed systemd unit to learn how to do a more in-depth check of a failing systemd unit.

Did the hardening profile work right away or still some issues? Send a quick message!

Frequently Asked Questions

How to use systemctl edit?

Run systemctl with the 'edit' subcommand and service.

systemctl edit UNIT.service

See full answer at How to use systemctl edit to change a service?

Feedback

Small picture of Michael Boelen

This article has been written by our Linux security expert Michael Boelen. With focus on creating high-quality articles and relevant examples, he wants to improve the field of Linux security. No more web full of copy-pasted blog posts.

Discovered outdated information or have a question? Share your thoughts. Thanks for your contribution!

Mastodon icon

Related articles

Like to learn more? Here is a list of articles within the same category or having similar tags.