Securing your NGINX site behind Cloudflare
 Cloudflare NGINXHow to keep your origin IP private and server protected from DOS attacks? My currently iteration of server setup is as follows: Cloudflare front with the origin server running NGINX as a reverse proxy (like cipher.tools) or delivering static HTML & content (like alexbarter.me).
I run Rocky Linux on my VPS so bare that in mind when copying commands as the software which provides the command might not be installed or there may be different equivalents on which ever Linux distro you might be running.
Setup
Installing NGINX
First install the package
$ dnf install nginx
Then start the service (and enable on boot if desired)
$ systemctl start nginx
$ systemctl enable nginx
If there are other webservers running on port 80 otherwise NGINX will fail to start. You can check the error message by running
journalctl -xe
You can verify the installation went ok by visiting http://<server-ip>/
. You should be greeted by:
Adding DNS records with Cloudflare
Add your domain to Cloudflare with “+ Add site”, once the Nameservers have been updated and Cloudflare has detected this has occurred you can continue with the setup. Ensure Cloudflare is active an thus will proxy any A/AAAA records under “Overview” -> “Advanced Actions”. Then you can go to the DNS tab and add an A record for your origin IP…
The changes might take a few minutes to propagate. Once they do your domain should no longer resolve to the origin IP and instead resolve to one of Cloudflare’s IP. DDOS attacks are now mitigated! You can adjust the DOS sensitively on the Cloudflare dashboard).
Denying access via direct IP access
To lock down our NGINX instance we’ll make a “deny all, catch all” server configuration. This means that if a hostname (including direct IP access) has not been specified explicitly NGINX will serve a 403 Forbidden page.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
deny all;
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name _;
ssl_certificate "/etc/ssl/certs/ip/cert.crt";
ssl_certificate_key "/etc/ssl/certs/ip/cert.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
deny all;
}
Then add the domain configurations to /etc/nginx/conf.d/<domain-name>.conf
to keep them separate (assuming the default
include /etc/nginx/conf.d/*.conf;
line is still included in the main nginx configuration file).
Denying access from non-Cloudflare IPs
If someone with ill intent somehow does obtain what they believe to be your origin IP then we do not want them to be able to confirm it and access the site’s content bypassing Cloudflare. Someone could confirm it by checking the content returned given the correct host header:
$ curl https://<origin-ip>/ -H "host: <domain-name>" --include --insecure
HTTP/2 200
server: nginx/1.14.1
date: Sat, 16 Oct 2021 21:50:40 GMT
content-type: text/html
content-length: 1228
last-modified: Sat, 16 Oct 2021 20:03:56 GMT
etag: "616b302c-4cc"
accept-ranges: bytes
<!DOCTYPE html>
...
Using the geo module we can mark incoming requests that come through Cloudflare’s network (and later deny requests that do not) with the following:
geo $realip_remote_addr $cloudflare_ip {
default 0;
173.245.48.0/20 1;
103.21.244.0/22 1;
103.22.200.0/22 1;
103.31.4.0/22 1;
141.101.64.0/18 1;
108.162.192.0/18 1;
190.93.240.0/20 1;
188.114.96.0/20 1;
197.234.240.0/22 1;
198.41.128.0/17 1;
162.158.0.0/15 1;
104.16.0.0/13 1;
104.24.0.0/14 1;
172.64.0.0/13 1;
131.0.72.0/22 1;
2400:cb00::/32 1;
2606:4700::/32 1;
2803:f800::/32 1;
2405:b500::/32 1;
2405:8100::/32 1;
2a06:98c0::/29 1;
2c0f:f248::/32 1;
}
A list of Cloudflare’s IPs can be found at https://cloudflare.com/ips
This gives the ability to deny connections with a
403 status code in the server {}
block with:
if ($cloudflare_ip != 1) {
return 403;
}
Now if someone was to test the IP they would receive a 403 Forbidden page which does not tell them anything other than they have encountered a correctly configured NGINX instance.
$ curl https://<origin-ip>/ -H "host: <domain-name>" --include --insecure
HTTP/1.1 403 Forbidden
Server: nginx/1.14.1
Date: Sat, 16 Oct 2021 21:46:12 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.14.1</center>
</body>
</html>
Restoring the visitors IP
If using NGINX as a reverse proxy the IP address ($_SERVER['REMOTE_ADDRESS']
) passed to the downstream server will be
Cloudflare’s IP. Cloudflare provides the IP of the visitor’s request in the
CF-Connecting-IP
header.
To fix this we can set the real IP to the value in Cloudflare’s header and restrict this behaviour only to when the
request comes from a Cloudflare IP so that the CF-Connecting-IP
header can not be forged.
real_ip_header CF-Connecting-IP;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;