0%
BackEnd

Top 25 Nginx Web Server Best Practices for Maximum Security

Vivek Gite Vivek Gite
10 min read

Secure your Nginx web server with these 25 best practices, covering configuration, SELinux, firewalls, and more, expertly curated by Vivek Gite.

Nginx is a lightweight, high-performance web server and reverse proxy, powering over 13.5% of internet domains according to Netcraft. Its event-driven architecture tackles the C10K problem, making it a favorite for high-traffic sites like WordPress and GitHub. However, with great power comes the need for robust security. This comprehensive guide, inspired by Vivek Gite’s expertise, outlines 25 best practices to harden your Nginx server on Linux or Unix-like systems. Whether you’re a sysadmin or developer, these steps will protect your server from common threats while optimizing performance.

Nginx architecture diagram

Getting Started with Nginx Configuration

Nginx’s default configuration files and ports are the foundation of your setup. Understanding these is crucial for effective security management:

  • Configuration Directory: /usr/local/nginx/conf/ or /etc/nginx/ (main file: nginx.conf).
  • Document Root: /usr/local/nginx/html/ or /var/www/html/.
  • Log Location: /usr/local/nginx/logs/ or /var/log/nginx/.
  • Default Ports: HTTP (TCP 80), HTTPS (TCP 443).

Test configuration changes with:

# /usr/local/nginx/sbin/nginx -t

Sample output:

the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
configuration file /usr/local/nginx/conf/nginx.conf test is successful

Reload changes with:

# /usr/local/nginx/sbin/nginx -s reload

Stop the server with:

# /usr/local/nginx/sbin/nginx -s stop

Essential Security Practices

#1: Enable SELinux

Security-Enhanced Linux (SELinux) adds a robust access control layer to your kernel. Enable it on CentOS/RHEL systems to thwart attacks before they escalate. Check and adjust boolean settings:

# getsebool -a | less
# getsebool -a | grep off
# getsebool -a | grep on

SELinux booleans configuration Lock down unnecessary ‘on’ settings with setsebool to balance functionality and security, noting a 2-8% performance overhead.

#2: Use Minimal Mount Options

Isolate webpages on a separate partition (e.g., /dev/sda5 mounted at /nginx) with restrictive options:

LABEL=/nginx     /nginx          ext3   defaults,nosuid,noexec,nodev 1 2

Create the partition with fdisk and format with mkfs.ext3.

#3: Harden /etc/sysctl.conf

Configure kernel and networking settings to enhance security:

net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
kernel.exec-shield = 1
kernel.randomize_va_space = 1
net.ipv6.conf.default.router_solicitations = 0
net.ipv6.conf.default.accept_ra_rtr_pref = 0
net.ipv6.conf.default.accept_ra_pinfo = 0
net.ipv6.conf.default.accept_ra_defrtr = 0
net.ipv6.conf.default.autoconf = 0
net.ipv6.conf.default.dad_transmits = 0
net.ipv6.conf.default.max_addresses = 1
fs.file-max = 65535
kernel.pid_max = 65536
net.ipv4.ip_local_port_range = 2000 65000
net.ipv4.tcp_rmem = 4096 87380 8388608
net.ipv4.tcp_wmem = 4096 87380 8388608
net.core.rmem_max = 8388608
net.core.wmem_max = 8388608
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_window_scaling = 1

These settings prevent smurf attacks, enable syncookies, and optimize TCP buffers.

#4: Remove Unwanted Modules

Minimize the attack surface by compiling only necessary modules. Disable SSI and autoindex:

# ./configure --without-http_autoindex_module --without-http_ssi_module
# make
# make install

Check configurable modules with:

# ./configure --help | less

Optionally, edit src/http/ngx_http_header_filter_module.c to change the server header:

static char ngx_http_server_string[] = "Server: Ninja Web Server" CRLF;
static char ngx_http_server_full_string[] = "Server: Ninja Web Server" CRLF;

Add server_tokens off; in nginx.conf to hide version details on error pages.

#5: Use mod_security for Apache Backends

For backend Apache servers, install mod_security as an application-level firewall to block injection attacks.

#6: Install SELinux Policy for Nginx

Enhance SELinux protection with a targeted policy. Install dependencies:

# yum -y install selinux-policy-targeted selinux-policy-devel

Download and compile the policy:

# cd /opt
# wget 'http://downloads.sourceforge.net/project/selinuxnginx/se-ngix_1_0_10.tar.gz?use_mirror=nchc'
# tar -zxvf se-ngix_1_0_10.tar.gz
# cd se-ngix_1_0_10/nginx
# make

Install the module:

# /usr/sbin/semodule -i nginx.pp

#7: Implement a Restrictive Iptables Firewall

Block all traffic except essential services with this script:

#!/bin/bash
IPT="/sbin/iptables"
SERVER_IP=$(ifconfig eth0 | grep 'inet addr:' | awk -F'inet addr:' '{ print $2}' | awk '{ print $1}')
LB1_IP="204.54.1.1"
LB2_IP="204.54.1.2"
OTHER_LB=""
[[ "$SERVER_IP" == "$LB1_IP" ]] && OTHER_LB="$LB2_IP" || OTHER_LB="$LB1_IP"
PUB_SSH_ONLY="122.xx.yy.zz/29"
BLOCKED_IP_TDB=/root/.fw/blocked.ip.txt
SPOOFIP="127.0.0.0/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 169.254.0.0/16 0.0.0.0/8 240.0.0.0/4 255.255.255.255/32 168.254.0.0/16 224.0.0.0/4 240.0.0.0/5 248.0.0.0/5 192.0.2.0/24"
BADIPS=$( [[ -f ${BLOCKED_IP_TDB} ]] && grep -E -v "^#|^$" ${BLOCKED_IP_TDB})
PUB_IF="eth0"
LO_IF="lo"
VPN_IF="eth1"
echo "Setting LB1 $(hostname) Firewall..."
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP
$IPT -A INPUT -i ${LO_IF} -j ACCEPT
$IPT -A OUTPUT -o ${LO_IF} -j ACCEPT
$IPT -A INPUT -i ${VPN_IF} -j ACCEPT
$IPT -A OUTPUT -o ${VPN_IF} -j ACCEPT
$IPT -A INPUT -i ${PUB_IF} -p tcp ! --syn -m state --state NEW -j DROP
$IPT -A INPUT -i ${PUB_IF} -f -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags ALL ALL -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags ALL NONE -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix " NULL Packets "
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags ALL NONE -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags SYN,FIN SYN,FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix " XMAS Packets "
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags FIN,ACK FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix " Fin Packets Scan "
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags FIN,ACK FIN -j DROP
$IPT -A INPUT -i ${PUB_IF} -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
$IPT -A INPUT -i ${PUB_IF} -m pkttype --pkt-type broadcast -j LOG --log-prefix " Broadcast "
$IPT -A INPUT -i ${PUB_IF} -m pkttype --pkt-type broadcast -j DROP
$IPT -A INPUT -i ${PUB_IF} -m pkttype --pkt-type multicast -j LOG --log-prefix " Multicast "
$IPT -A INPUT -i ${PUB_IF} -m pkttype --pkt-type multicast -j DROP
$IPT -A INPUT -i ${PUB_IF} -m state --state INVALID -j LOG --log-prefix " Invalid "
$IPT -A INPUT -i ${PUB_IF} -m state --state INVALID -j DROP
$IPT -N spooflist
for ipblock in $SPOOFIP
do
  $IPT -A spooflist -i ${PUB_IF} -s $ipblock -j LOG --log-prefix " SPOOF List Block "
  $IPT -A spooflist -i ${PUB_IF} -s $ipblock -j DROP
done
$IPT -I INPUT -j spooflist
$IPT -I OUTPUT -j spooflist
$IPT -I FORWARD -j spooflist
for ip in ${PUB_SSH_ONLY}
do
  $IPT -A INPUT -i ${PUB_IF} -s ${ip} -p tcp -d ${SERVER_IP} --destination-port 22 -j ACCEPT
  $IPT -A OUTPUT -o ${PUB_IF} -d ${ip} -p tcp -s ${SERVER_IP} --sport 22 -j ACCEPT
done
$IPT -A INPUT -i ${PUB_IF} -p icmp --icmp-type 8 -s 0/0 -m state --state NEW,ESTABLISHED,RELATED -m limit --limit 30/sec -j ACCEPT
$IPT -A OUTPUT -o ${PUB_IF} -p icmp --icmp-type 0 -d 0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -i ${PUB_IF} -p tcp -s 0/0 --sport 1024:65535 --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPT -A OUTPUT -o ${PUB_IF} -p tcp --sport 80 -d 0/0 --dport 1024:65535 -m state --state ESTABLISHED -j ACCEPT
$IPT -A OUTPUT -o ${PUB_IF} -p udp --dport 123 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPT -A INPUT -i ${PUB_IF} -p udp --sport 123 -m state --state ESTABLISHED -j ACCEPT
$IPT -A OUTPUT -o ${PUB_IF} -p tcp --dport 25 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPT -A INPUT -i ${PUB_IF} -p tcp --sport 25 -m state --state ESTABLISHED -j ACCEPT
$IPT -A INPUT -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix " DEFAULT DROP "
$IPT -A INPUT -j DROP
exit 0

This allows HTTP (80), ICMP ping, NTP (123), and SMTP (25) while blocking threats.

#8: Mitigate Buffer Overflow Attacks

Limit buffer sizes in nginx.conf to prevent overflows:

## Start: Size Limits & Buffer Overflows ##
client_body_buffer_size 1k;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
## END: Size Limits & Buffer Overflows ##

Control timeouts for performance:

## Start: Timeouts ##
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 5 5;
send_timeout 10;
## End: Timeouts ##

#9: Control Simultaneous Connections

Limit connections per IP with the HttpLimitZone module:

limit_zone slimits $binary_remote_addr 5m;
limit_conn slimits 5;

#10: Restrict to Your Domain

Allow only your domain requests:

if ($host !~ ^(example.com|www.example.com)$) {
  return 444;
}

#11: Limit Available Methods

Restrict to GET, HEAD, POST:

if ($request_method !~ ^(GET|HEAD|POST)$) {
  return 444;
}

#12: Block Malicious User-Agents

Deny specific bots:

if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
  return 403;
}
if ($http_user_agent ~* msnbot|scrapbot) {
  return 403;
}

#13: Block Referral Spam

Filter spammy referers:

if ($http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen)) {
  return 403;
}

#14: Prevent Image Hotlinking

Stop hotlinking with:

location /images/ {
  valid_referers none blocked www.example.com example.com;
  if ($invalid_referer) {
    return 403;
  }
}

Or rewrite to a banned image:

valid_referers blocked www.example.com example.com;
if ($invalid_referer) {
  rewrite ^/images/uploads.*\.(gif|jpg|jpeg|png)$ http://www.example.com/banned.jpg last;
}

#15: Enforce Directory Restrictions

Limit access by IP:

location /docs/ {
  deny 192.168.1.1;
  allow 192.168.1.0/24;
  deny all;
}

Password-protect directories:

# mkdir /usr/local/nginx/conf/.htpasswd/
# htpasswd -c /usr/local/nginx/conf/.htpasswd/passwd vivek
location ~ /(personal-images/.*|delta/.*) {
  auth_basic "Restricted";
  auth_basic_user_file /usr/local/nginx/conf/.htpasswd/passwd;
}

#16: Secure Nginx with SSL

Encrypt traffic with SSL:

  • Create a self-signed certificate (see CentOS/RHEL guide).
  • Configure with Let’s Encrypt (Debian/Ubuntu or Alpine guides).

#17: Harden PHP Security

Edit /etc/php.ini:

disable_functions = phpinfo, system, mail, exec
max_execution_time = 30
max_input_time = 60
memory_limit = 8M
post_max_size = 8M
file_uploads = Off
upload_max_filesize = 2M
display_errors = Off
safe_mode = On
safe_mode_exec_dir = php-required-executables-path
safe_mode_allowed_env_vars = PHP_
expose_php = Off
log_errors = On
register_globals = Off
post_max_size = 1K
cgi.force_redirect = 0
file_uploads = Off
sql.safe_mode = On
allow_url_fopen = Off

#18: Run Nginx in a Chroot Jail

Isolate Nginx with a chroot setup or use containers like FreeBSD jails or LXD.

#19: Limit Connections per IP at Firewall

Throttle connections with iptables:

/sbin/iptables -A INPUT -p tcp --dport 80 -i eth0 -m state --state NEW -m recent --set
/sbin/iptables -A INPUT -p tcp --dport 80 -i eth0 -m state --state NEW -m recent --update --seconds 60 --hitcount 15 -j DROP
service iptables save

#20: Secure File Permissions

Ensure /nginx files aren’t owned by the nginx user:

# find /nginx -user nginx
# find /usr/local/nginx/html -user nginx

Set ownership to root:

# chown -R root:root /usr/local/nginx/html/

Remove backup files:

# find /nginx -name '.?*' -not -name .ht* -or -name '*~' -or -name '*.bak*' -or -name '*.old*' -delete

#21: Restrict Outgoing Connections

Block nginx user outbound traffic with iptables:

/sbin/iptables -A OUTPUT -o eth0 -m owner --uid-owner nginx -j DROP

#22: Keep Software Updated

Update regularly:

  • Debian/Ubuntu: sudo apt-get update && sudo apt-get upgrade
  • RHEL/CentOS: sudo yum update
  • Alpine: apk update && apk upgrade

#23: Prevent Clickjacking

Add in nginx.conf:

add_header X-Frame-Options SAMEORIGIN;

#24: Disable Content-Type Sniffing

Add in nginx.conf:

add_header X-Content-Type-Options nosniff;

#25: Enable XSS Protection

Add in nginx.conf:

add_header X-XSS-Protection "1; mode=block";

Conclusion

Implementing these 25 best practices will transform your Nginx server into a fortress, safeguarding against common vulnerabilities while maintaining high performance. From SELinux and iptables to SSL and PHP hardening, Vivek Gite’s insights provide a solid foundation. Regularly monitor logs and update your setup to stay ahead of threats. Secure your server today and enjoy a robust web hosting experience!

More in BackEnd