How to ensure all public web traffic traffic flows through your load balancer(s)

By default, most cloud servers are configured to allow traffic via ports 80 and 443 and to have public DNS entries on the cloud provider’s “internal” domain. This means that these servers can still be hit directly even if you have set up load balancers and/or reverse proxies to act as your public endpoints. This can leave you vulnerable to DDOS and other attacks.

If you need the added security of ensuring that all traffic flows exclusively through your preferred endpoints (load balancers), you will need to set up the following:

  1. A reverse proxy and/or security service (such as Cloudflare) that acts as your public endpoint
  2. A private endpoint at your cloud provider (typically a load balancer) that only accepts traffic from your reverse proxy
  3. Host header protection to ensure your servers only respond via your service’s correct HOST header.

Reverse proxy services like Cloudflare are a great defense against DDOS and bot attacks, but attackers can still use your DNS history to look up the IP addresses of your servers and then hit them directly.

To mitigate this, you can set your cloud firewall to only accept requests to your load balancer that originate from your reverse proxy service - see the links below. In Cloudflare’s case you can find those IP ranges here.

However even with a reverse proxy and firewall in place, it is still sometimes possible for determined attackers to circumvent these measures and hit your servers directly. To mitigate this, we can set up our Host Headers to reject requests that do not originate from the correct hostname.

To do this in nginx you need to make sure that your current server block has the correct server_name directive with your hostname in it. Then, you simply make another server block, but this time you set server_name to something like an underscore _ and have it return nothing or a 400 (bad request) error. If you need SSL you can use a snakeoil certificate or self-signed certificate.

If your domain was example.com, the host entries would look as follows:

server {
	listen 80 443 ssl;
	ssl_certificate /snakeoil.crt;
	ssl_certificate_key /snakeoil.key;
	server_name _;
	return 400;
}
server {
	listen 443 ssl;
	ssl_certificate /examplecom.crt;
	ssl_certificate_key /examplecom.key;
	server_name example.com;
	location / {
		...
	}
}