Proxying Cloudfront with Nginx

Don’t do this.

You’re still here? Cool, let’s continue. For complicated reasons involving a legacy on-premises application I had a static site on AWS that I needed to route to with an Nginx reverse proxy. The application architecture included several locations, each of which was proxied to a different port on an on-premises server. The landing page, and only the landing page (and its assets), are on Cloudfront. For historical reasons, I needed to have this entire collection under a single DNS name.

Location, location …

The first step was modifying the Nginx proxy to have a separate location for the root landing page and its assets, while leaving everything else untouched. Serverfault came to the rescue on this one:

1
2
3
4
5
6
7
8
9
10
location = / {

proxy_pass https://yourdomain.cloudfront.net;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Per the Nginx documentation, using the equals operator (=) in a location block specifies an exact match.

SNI

That configuration was my first attempt and it didn’t work:

1
2022/11/29 15:43:41 [error] 7701#7701: *29249 SSL_do_handshake() failed (SSL: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol) while SSL handshaking to upstream, client: xxx.xxx.xxx.xxx, server: example.net, request: "GET / HTTP/1.1", upstream: "https://xxx.xxx.xxx.xxx:443/", host: "example.net"

The main issue here is that we’re passing a request back to Cloudfront and not indicating which domain it’s for. Cloudfront uses Server Name Indication (SNI) so we have to help it out a little. I went through several guides and bits of documentation before I ran across a blog post from Dave Long that put it all together. This is my final configuration on the Nginx side:

1
2
3
4
5
6
7
8
9
10
11
12
13
location = / {

proxy_pass https://yourdomain.cloudfront.net;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_ssl_name example.net;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
proxy_ssl_server_name on;
}

Cloudfront

The Cloudfront piece was straightforward. Since we’re doing TLS with a custom domain, I ensured that the TLS certificate had that name added and that the name was added as an additional domain name.