Top 25 Nginx Web Server Best Practices for Maximum Security
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.

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
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!