For testing and blogging(?) purposes, I have a few websites up & running.
Until recently, they’ve been behind an f5 Application Delivery Controller (ADC), or more traditionally named: loadbalancer.
No practical reason other than it gives me the environment I want to play around with f5 features (ASM, etc.) with “real” traffic, although 99.5% from myself and family, without needing to worry about breaking something for anyone else.
A while ago I realized that I would eventually need to familiarize myself with nginx at some point.
I wasn’t quite sure how it would compare to f5, as my only experience with nginx is when it comes bundled with something else.
So the decision was made: I’d scrap my f5 lab environment and try to replace it with nginx instead.
Obviously I expected to migrate some of the tweaks from f5 over to nginx, and try out nginx “App Protect” to replace f5 ASM.
Now, since I wanted to try out the NGINX Plus and App Protect, while not “free” like the open source NGINX, getting trial keys was minimal effort.
Aside from App Protect, the main NGINX+ feature I’m looking at is the active health checks, but here you can compare the features.
At the beginning of my journey in early December, my go-to linux distro was Centos. So I installed Centos 8 on a virtual Machine to start testing.
Shortly after, the CentOS team announced some changes to the future of CentOS, which forced me to re-evaluate my “default” go-to distro.
(Another story, but I decided to go with Ubuntu.)
Anyway, turns out that the App Protect team is… uh, “lagging” a bit behind.
In December 2020, App Protect was neither supported on Centos 8 or Ubuntu 20 LTS.
I fired up a Ubuntu 18 machine to test out the basics of App Protect, but decided I’d go into more testing once they would start supporting Ubuntu 20 and scrapped the App Protect tests.
Getting started was relatively easy.
The installation guide on nginx.com was pretty straight forward and easy to follow.
Most other guides found online seemed to be somewhat identical.
And finding info & resourced online seemed to be
Aside from the installation, here’s a short example of what I needed to do:
- put a simple wordpress site, “escort.is”, behind NGINX.
- Use SSL towards clients, but cleartext towards backend.
- (wordpress is running in a docker environment on port 80)
- redirect http -> https
- Add the X-Forwarded-Proto header so wordpress doesn’t go crazy with http/https
- Limit exposure to /wp-admin and /wp-login.php to a few IPs.
- Limit exposure to /xmlrpc.php to a few IPs.
- Use SSL towards clients, but cleartext towards backend.
Here’s the “escort.conf” file I’ve ended up with so far.
For context:
192.0.2.10 is the docker host I’m running on, and 8094 is the exposed port for said wordpress instance.
The docker instance is within then 172.19.0.0/16 range
My personal IP has been modified to 192.0.2.50.
match server_success {
status 200-399;
body ~ "success";
}
upstream escort-docker {
zone backend 64k;
server 192.0.2.10:8094;
}
server {
listen 80;
server_name escort.is www.escort.is;
return 301 https://$host$request_uri;
location / {
proxy_set_header Host $host;
proxy_pass http://escort-docker;
}
}
server {
listen 443 ssl;
server_name escort.is;
ssl_certificate /etc/nginx/ssl/escort.cer;
ssl_certificate_key /etc/nginx/ssl/escort.key;
proxy_cache mycache;
location / {
health_check uri=/status.txt match=server_success;
status_zone wordpress_zones;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://escort-docker;
location ~ ^(/xmlrpc.php) {
allow 192.0.2.50/32;
allow 192.0.2.10/32;
allow 172.19.0.0/16;
deny all;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://escort-docker;
}
location ~ ^(/wp-login.php|/wp-admin) {
allow 192.0.2.50/32;
allow 192.0.2.10/32;
allow 172.19.0.0/16;
deny all;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://escort-docker;
}
}
location /403.html {
root /usr/share/nginx/html;
}
}
I will most probably learn later on that this is not the best way to do this.
I wasn’t successful with the use of variables on first attempt, which seems to be a religious discussion anyway.
And inheritance doesn’t work with proxy_pass.
So to review the sections:
Health check, not really a part of this configuration file but kept as a separate file.
match server_success {
# referenced in the server section
status 200-399;
body ~ "success";
# expects to find "success" in the body
}
Upstream/backend
upstream escort-docker {
zone backend 64k; #The "zone" command was added so I would see statistics for this on NGINX+ Dashboard.
server 192.0.2.10:8094; #The wordpress site is at this ip:port
}
Server(80)
server {
listen 80; #Listening on port 80
server_name escort.is www.escort.is; # one or more server names
return 301 https://$host$request_uri; # 301 redirect from http to https, keeping the full URI
location / {
proxy_set_header Host $host;
proxy_pass http://escort-docker; #doesn't really do anything here, but kept for testing purposes.
}
}
Server(443)
server {
listen 443 ssl;
# listening on port 443, using SSL.
server_name escort.is;
# one or more server names
ssl_certificate /etc/nginx/ssl/escort.cer;
# the public SSL key
ssl_certificate_key /etc/nginx/ssl/escort.key;
# the private SSL key
proxy_cache mycache;
# used for content caching which I haven't been able to get working correctly yet, so ignore...
location / {
health_check uri=/status.txt match=server_success;
# health check, does a GET requeest to "/status.txt" and expects it to contain "success", see the health check section on top
status_zone wordpress_zones;
# used for dashboard statistics similar to zone in the upstream definition.
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https; # If missing, the wordpress will go into endless redirects.
proxy_pass http://escort-docker; # referncing the upstream section, sending the traffic here.
location ~ ^(/xmlrpc.php) { # nested location section, disallowing access to /xmlrpc.php, except for a handful of IP addresses.
allow 192.0.2.50/32;
allow 192.0.2.10/32;
allow 172.19.0.0/16;
deny all;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://escort-docker; # Now, the "proxy_pass" doesn't work with inheritance, I haven't tested the proxy_set yet.
}
location ~ ^(/wp-login.php|/wp-admin) { # same as above. The tilde(~) indicates that it's using regex matching.
allow 192.0.2.50/32;
allow 192.0.2.10/32;
allow 172.19.0.0/16;
deny all;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://escort-docker;
}
}
location /403.html {
# I wonder if I could do this globally, but essentially if escort.is/403.html is called, it will be served with a local file within the following directory...
root /usr/share/nginx/html;
}
}
Whats missing?
Well, SSL cipher configuration, default values will not give me an A+ or even an A rating on SSL Labs.
And probably more tweaking should be included.