Launching the container
This how-to guide will take you through the steps to setup a reverse proxy on your system by using a LXD container to run HAProxy and then configure it pass networking traffic to internal containers. This guide will assume that your system is already configured as an LXD server.
Before setting up the reverse proxy, run the following command to get a list of IP addresses for your containers, noting the address of the instances to forward traffic to:
$ lxc list
Start by launching a new instance using the Ubuntu 20.04 image. To launch the new instance and name it haproxy use the following command:
$ lxc launch ubuntu:20.04 haproxy
This will create a base container where we will install HAProxy. Once the command finishes the container should be running. We will need to setup port forwarding (proxy port) for the TCP/UDP ports we want HAProxy to handle. The plan is to have port 80 (http) and 443 (https) forwarded to the haproxy container and from there it will redirect traffic to the appropriate internal instances. To forward the host LXD server ports to the haproxy instance, we can either edit the haproxy container configuration directly or create a profile for the port forwarding and then attached it to the container. In this tutorial I will edit the container’s configuration directly. See https://lxdware.com/forwarding-host-ports-to-lxd-instances/(opens in a new tab) for instructions on creating profiles as an option.
Use the following commands to edit the configuration of the haproxy container and forward both ports 80 and 443 from the host LXD server to the container:
$ lxc config device add haproxy hostport80 proxy listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:80
$ lxc config device add haproxy hostport443 proxy listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:443
Now it is time to connect into the container and setup the software. Use the following command to obtain a bash shell connection to the instance, use the exit command at anytime to leave the shell:
$ lxc exec haproxy /bin/bash
The following commands will now be run inside the haproxy container. Use the following command to install the HAProxy package:
$ apt update && apt install haproxy -y
Setting up the proxy config
With HAProxy installed, we will need to add a frontend and backend that listens for ports 80 and 443. Since port 443 will listen for encrypted SSL traffic, we need to create a separate frontend handling SSL traffic. Both frontends listen for the destination URL and assign an ACL Host to traffic matching the URL. If the traffic is matched to an ACL host, it is then assigned the appropriate backend to send traffic to. To edit the configuration file use the following command:
Append the highlighted code below. In the example two different internal containers are both using port 80 and 443. One is running WordPress and the other Nextcloud. If the correct URL is coming into HAProxy, the traffic is then assigned a backend which define the internal instance to direct traffic to.
global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend localhost80 bind *:80 mode tcp #Set acl based on domain name acl host_nextcloud hdr(host) -i srv2.test.internal acl host_wordpress hdr(host) -i srv1.test.internal #Set backend for each acl use_backend nextcloud_http if host_nextcloud use_backend wordpress_http if host_wordpress frontend localhost443 bind *:443 option tcplog mode tcp acl tls req.ssl_hello_type 1 tcp-request inspect-delay 5s tcp-request content accept if tls #Set acl based on the domain name acl is_nextcloud req.ssl_sni -i srv2.test.internal acl is_wordpress req.ssl_sni -i srv1.test.internal #Set backend for each acl use_backend nextcloud_https if is_nextcloud use_backend wordpress_https if is_wordpress backend nextcloud_http mode tcp server ubuntu-nextcloud 10.187.151.36:80 check backend wordpress_http mode tcp server ubuntu-wordpress 10.187.151.36:80 check backend nextcloud_https mode tcp option ssl-hello-chk server ubuntu-nextcloud 10.187.151.36:443 check backend wordpress_https mode tcp option ssl-hello-chk server ubuntu-wordpress 10.187.151.36:443 check
Starting and Enabling HAProxy
Now that the configuration has been added it is time to restart HAProxy to apply the changes. In addition to restarting HAProxy it is a good idea to enable the service as well, as not all distributions enable it automatically. Run the following commands to restart and enable HAProxy:
$ systemctl restart haproxy $ systemctl enable haproxy
Congratulations! The container is now setup. Exit from the shell terminal and return to your LXD host server by using the command:
The backend configuration in example lists only a single instance to forward traffic to for each backend. Additional instances can be defined in the same backend allowing for load balancing if two or more servers are running the same software in a High Availability (HA) setup. HAProxy also has the ability to monitor (check) the health of the backend instance and it can be configured to display a statics web page to show the health of the backend.
Also, frontends can listen to more than one port. Since port 443 uses SSL encryption, a separate frontend was created specifically to handle the SSL traffic. This example forwards SSL traffic directly to an internal instance using TCP mode, where the internal instance handles the SSL certificates. HAProxy can be configured to handle the SSL certificates instead.
2 thoughts on “Simple LXD reverse proxy using HAProxy”
I used this tutorial to setup HAProxy + LXD proxy devices + LXD containers + Nginx welcome site (for testing). Only setup for port 80, initially.
Ran into an issue where I was getting ERR_EMPTY_RESPONSE and ERR_CONNECTION_RESET errors, upon initial page load then it would be intermittent.
After running a config file check I received warnings about ‘mode http’ being needed. So I changed the ‘mode tcp’ in the settings from the tutorial to ‘mode http’ in both the frontend and backend. No more ERR_* type page load errors (so far) after doing that. Wanted to post this here in case anyone runs into the same issue.
I also posted a support thread on the LXD site at https://discuss.linuxcontainers.org/t/13605 in case it’s useful for anyone.
I am glad that you got it working. Thanks for updating me with your issue. I will test it out and update the page