The background
I manage my services (typically) via domain names and HTTP HOST
headers. This allows me to have memorable names, like blog.lolnope.us
and plex.lolnope.us
, rather than having to remember ports like lolnope.us:4433
and lolnope.us:32400
.
An added benefit is the reuse of port 443
for multiple HTTPS-encrypted sites. I can decrypt all HTTPS-based traffic with a single "frontend", and then route it based on the HOST
header.
You can also route HTTPS traffic without decrypting if your setup supports Server Name Indication.
Technology giveth, and technology taketh away
Well, some nice pros (and cons) come from such a set up, primarily revolving around HTTPS supporting features. One such feature, HSTS, can fool you. :(
HSTS, or HTTP Strict Transport Security, is a mechanism which locally forces your browser to respect the HTTPS-ness of a website without being fed an HTTP redirect.
Usually, the interaction looks like:
$ curl -vskL http://blog.lolnope.us > /dev/null
* Connected to blog.lolnope.us (24.4.223.218) port 80 (#0)
> GET / HTTP/1.1
> Host: blog.lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.13.7
< Date: Mon, 02 Apr 2018 23:13:18 GMT
< Content-Type: text/html
< Content-Length: 185
< Connection: keep-alive
< Location: https://blog.lolnope.us/
<
* Cipher spec exchange...
> GET / HTTP/2
> Host: blog.lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/2 200
< server: nginx/1.13.7
< date: Mon, 02 Apr 2018 23:13:19 GMT
< content-type: text/html; charset=utf-8
< content-length: 14465
< x-powered-by: Express
< cache-control: public, max-age=0
< etag: W/"3881-dyhYx/NYeK1AfEa7n0EikCDMvAM"
< vary: Accept-Encoding
< strict-transport-security: max-age=31536000
<
{ [3834 bytes data]
* Connection #1 to host blog.lolnope.us left intact
Here, I am being explicitly told via HTTP 301 that I need to go to Location: https://blog.lolnope.us/
, and I'm being told that this website should use HSTS for a time: strict-transport-security: max-age=31536000
.
Google Chrome remembers this HSTS header, and will HTTP 307 (Internal Redirect) you if you attempt to access it via HTTP:
Request URL: http://blog.lolnope.us/
Request Method: GET
Status Code: 307 Internal Redirect
Referrer Policy: no-referrer-when-downgrade
This comes from Chrome's Developer Tool's network inspector.
The browser never makes an HTTP request, it automatically "redirects" itself to HTTPS.
The problem here is... what if something is broken with the HTTP -> HTTPS redirect on the site itself?
Answer: Until that max-age
expires, you won't have a clue. :(
Domains were busted
So if you haven't guessed it already, I discovered that my HTTP -> HTTPS redirect was broken for a time. The root cause had to do with how I was dealing with SNI, and routing HOST
s in my Docker environment.
The solution? Explicitly handle some redirects outside of nginx via a simple and lightweight Docker instance.
Using ianneub/redirect
(because I'm too lazy to implement my own), I'm able to control what domains redirect to where:
# Since this is listed first as a "backend", this is the default destination(?)
# Redirect all "lolnope.us" requests to "www.lolnope.us"
lolnope-us-redirect:
container_name: lolnope-us-redirect
depends_on:
- nginx
environment:
VIRTUAL_HOST: lolnope.us
LETSENCRYPT_HOST: lolnope.us
LETSENCRYPT_EMAIL: [email protected]
REDIRECT: 'https://www.lolnope.us'
image: ianneub/redirect
networks:
- public
restart: always
# Redirect all "www.lolnope.us" requests to "blog.lolnope.us"
www-lolnope-us-redirect:
container_name: www-lolnope-us-redirect
depends_on:
- nginx
environment:
VIRTUAL_HOST: www.lolnope.us
LETSENCRYPT_HOST: www.lolnope.us
LETSENCRYPT_EMAIL: [email protected]
REDIRECT: 'https://blog.lolnope.us'
image: ianneub/redirect
networks:
- public
restart: always
I then have a final backend which responds to blog.lolnope.us
(which is this blog!). 📝
Putting it all together
Making an HTTP request to lolnope.us
, all the way to landing on this blog:
$ curl -vskL http://lolnope.us > /dev/null
* Rebuilt URL to: http://lolnope.us/
* Connected to lolnope.us (24.4.223.218) port 80 (#0)
> GET / HTTP/1.1
> Host: lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.13.7
< Date: Wed, 04 Apr 2018 23:55:05 GMT
< Content-Type: text/html
< Content-Length: 185
< Connection: keep-alive
< Location: https://lolnope.us/
<
* Issue another request to this URL: 'https://lolnope.us/'
* Connected to lolnope.us (24.4.223.218) port 443 (#1)
* Cipher spec exchange...
> GET / HTTP/2
> Host: lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/2 301
< server: nginx/1.13.7
< date: Wed, 04 Apr 2018 23:55:05 GMT
< content-type: text/html
< content-length: 185
< location: https://www.lolnope.us/
< strict-transport-security: max-age=31536000
<
* Issue another request to this URL: 'https://www.lolnope.us/'
* Connected to www.lolnope.us (24.4.223.218) port 443 (#2)
* Cipher spec exchange...
> GET / HTTP/2
> Host: www.lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/2 301
< server: nginx/1.13.7
< date: Wed, 04 Apr 2018 23:55:05 GMT
< content-type: text/html
< content-length: 185
< location: https://blog.lolnope.us/
< strict-transport-security: max-age=31536000
<
* Issue another request to this URL: 'https://blog.lolnope.us/'
* Connected to blog.lolnope.us (24.4.223.218) port 443 (#3)
* Cipher spec exchange...
> GET / HTTP/2
> Host: blog.lolnope.us
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/2 200
< server: nginx/1.13.7
< date: Wed, 04 Apr 2018 23:55:06 GMT
< content-type: text/html; charset=utf-8
< content-length: 14465
< x-powered-by: Express
< cache-control: public, max-age=0
< etag: W/"3881-dyhYx/NYeK1AfEa7n0EikCDMvAM"
< vary: Accept-Encoding
< strict-transport-security: max-age=31536000
<