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
Installing HAProxy
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:
$ /etc/haproxy/haproxy.cfg
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:
$ exit
Additional thoughts
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.
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.
John,
I am glad that you got it working. Thanks for updating me with your issue. I will test it out and update the page