NGINX with QUIC+HTTP/3

We’ve recently added LL-HLS to stream and one of the soft requirements for the feature is the use of an HTTP/2 or HTTP/3 reverse proxy. There are many different reverse proxies that you can use (although Apache is not recommended). NGINX is one reverse proxy that works well with aiohttp (the web server used by HA). I’ve been using a docker image built from the nginx-quic branch of NGINX and it has been working quite well for me, so I’m sharing my config here. There are other posts on creating SSL/TLS certificates and setting up NGINX with HA, so please reference those threads if you have any questions on those topics. Feel free to comment or edit this post if you have any suggestions or improvements.

Building the docker image:

Use the Dockerfile at the bottom of Our Roadmap for QUIC and HTTP/3 Support in NGINX - NGINX , but remove the argument --with-http_quic_module as the http_quic_module has been merged into the http_v3_module.
Also remove the lines:
</s> <s>COPY nginx.conf /etc/nginx/nginx.conf</s> <s>COPY www.example.com.crt /etc/ssl/www.example.com.crt</s> <s>COPY www.example.com.key /etc/ssl/www.example.com.key</s> <s>COPY index.html /etc/nginx/html/index.html</s> <s>
There is a template Dockerfile at the bottom of Our Roadmap for QUIC and HTTP/3 Support in NGINX - NGINX , but this Dockerfile uses BoringSSL which does not support OCSP stapling. There is a fork of OpenSSL at GitHub - quictls/openssl: TLS/SSL and crypto library with QUIC APIs which supports QUIC and also supports OCSP stapling (this will slightly speed up client requests, and it is also a requirement for using TLSv1.3 with iOS clients). To build a docker image for nginx-quic using this branch, use the following Dockerfile:

FROM nginx AS build

WORKDIR /src
RUN apt-get update && \
    apt-get install -y git gcc make g++ cmake perl libunwind-dev golang && \
    git clone https://github.com/quictls/openssl.git && \
    mkdir openssl/build && \
    cd openssl/build && \
        cd .. && \
    ./Configure && \
    make

RUN apt-get install -y mercurial libperl-dev libpcre3-dev zlib1g-dev libxslt1-dev libgd-ocaml-dev libgeoip-dev && \
    hg clone https://hg.nginx.org/nginx-quic   && \
    hg clone http://hg.nginx.org/njs -r "0.6.2" && \
    cd nginx-quic && \
    hg update quic && \
    auto/configure `nginx -V 2>&1 | sed "s/ \-\-/ \\\ \n\t--/g" | grep "\-\-" | grep -ve opt= -e param= -e build=` \
                   --build=nginx-quic --with-debug  \
                   --with-http_v3_module --with-stream_quic_module \
                   --with-openssl="/src/openssl" && \
    make

FROM nginx
COPY --from=build /src/nginx-quic/objs/nginx /usr/sbin
RUN /usr/sbin/nginx -V > /dev/stderr
# The following line is the port that nginx will run on.
EXPOSE 443

Place this Dockerfile in a directory named nginx-quic.
To build the container image, run docker build nginx-quic (sudo permissions may be required). After it’s done, you should get the message “Successfully built <image id>”.

Creating the container:

The docker image is now built, and you can use it like you would the regular nginx docker image. You can create a container from the image using the following docker-compose.yaml:

version: "3.9"
services:
  nginx:
    container_name: nginx
    image: <image id>
    volumes:
      - <path to directory outside container>/ssl:/ssl
      - <path to directory outside container>/conf.d:/etc/nginx/conf.d
    network_mode: host

Above, <path to directory outside container> is a folder somewhere for this container, and beneath this folder are two subfolders named ssl and conf.d. The ssl directory will contain your key, certificates, and dh parameters, while conf.d will contain your nginx.conf file.

Creating the nginx.conf file:

The following is my nginx.conf:

map $http_upgrade $connection_upgrade {
    default     upgrade;
    ''          close;
}

upstream homeassistant {
    server 127.0.0.1:8123;
    keepalive 32;
}

server {

    listen [::]:443 http3 reuseport ipv6only=off;
    listen [::]:443 ssl http2 ipv6only=off;

    server_name <your host name>;
    ssl_certificate /ssl/fullchain.pem;
    ssl_certificate_key /ssl/privkey.pem;
    ssl_dhparam /ssl/dhparams.pem;

    # Similar to the modern configuration from https://ssl-config.mozilla.org/
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:1m; # 1m should be more than enough
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;

    # Using proxy buffers may free up HA sooner on slower connections.
    # Make sure that you don't see any "an upstream response is buffered to a temporary file" messages in your logs
    # If you do, increase the buffer size
    proxy_buffers 1024 4k;

    proxy_connect_timeout 60;
    proxy_read_timeout 60;
    proxy_send_timeout 60;
    proxy_intercept_errors off;
    proxy_http_version 1.1;

    proxy_set_header                X-Forwarded-For         $proxy_add_x_forwarded_for;
    proxy_set_header                Host                    $http_host;
    proxy_set_header                X-Real-IP               $remote_addr;
    proxy_set_header                X-Forwarded-Proto       $scheme;
    proxy_set_header                Connection              "";

    location / {
        add_header                  Alt-Svc 'h3=":443"; ma=86400';
        add_header                  Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
        proxy_pass                  http://homeassistant;
    }

    location /api/websocket {
        proxy_set_header            Upgrade                 $http_upgrade;
        proxy_set_header            Connection              $connection_upgrade;
        proxy_pass                  http://homeassistant/api/websocket;
    }

    # replace with the IP address of your resolver
    resolver 9.9.9.9;
}

Port forwarding:

Since QUIC uses UDP, you’ll have to remember to configure your router to forward UDP in addition to TCP on your listening port (port 443 in the nginx.conf above).

Hopefully some of you can give this a try and see if you notice any speedup in Lovelace. If you use stream you can try pairing this setup with LL-HLS.

HAProxy 2.6.0 was released a few days ago and the new version supports QUIC+HTTP/3. NGINX hasn’t seen much progress on the QUIC+HTTP/3 front, and development curiously stopped several months ago (unclear whether this was due to company politics or geopolitics). Oddly enough, development on the branch has seemingly resumed as of last week, but it’s unclear when they will make an official release.
For someone interested in HTTP/3 without an existing reverse proxy setup, it probably makes more sense to go with HAProxy for now.

1 Like