« Back to Hardening profiles for systemd

Nginx hardening profile

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.

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

Applying this profile

Open the unit file for nginx with the built-in editor.

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: 1 [2024-06-24]
################################################################################
# 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
NoExecPaths=/

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
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_DAC_OVERRIDE CAP_SETGID CAP_SETUID

# Only allow the usage of system calls for own hardware platform. Advised when using RestrictAddressFamilies
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


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

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


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

# Do not gain new privileges
NoNewPrivileges=yes


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

# Do not allow personality switch
LockPersonality=true


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

PrivateDevices=yes

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

# Do not allow changing the system clock
ProtectClock=yes

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

# Do not allow changing the hostname
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
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
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
RestrictNamespaces=yes

# Realtime scheduling not allowed
RestrictRealtime=yes

# Do not allow setting set-user-ID and set-group-ID
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
# ===============================

# Files are created by default with 700/600
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.

Everything working?

Perfect!

If it does (or not), share your feedback!

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