Posted on 2 Comments

Simple LXD reverse proxy using HAProxy

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.

2 thoughts on “Simple LXD reverse proxy using HAProxy

  1. 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.

    1. 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

Leave a Reply

Your email address will not be published. Required fields are marked *