{"id":2420,"date":"2018-08-16T07:00:11","date_gmt":"2018-08-16T07:00:11","guid":{"rendered":"https:\/\/blog.ssdnodes.com\/blog\/?p=2420"},"modified":"2025-05-18T13:07:18","modified_gmt":"2025-05-18T13:07:18","slug":"linux-containers-lxc-haproxy","status":"publish","type":"post","link":"https:\/\/www.ssdnodes.com\/blog\/linux-containers-lxc-haproxy\/","title":{"rendered":"Hosting Multiple Websites With Containers And HAProxy"},"content":{"rendered":"<div id=\"preview1\" class=\"g-b g-b--t1of2 split split-preview\">\n<div id=\"preview\" class=\"preview-html\">\n<p>Hosting multiple websites on a single VPS <a href=\"https:\/\/www.ssdnodes.com\/blog\/host-multiple-websites-docker-nginx\/\">via Docker<\/a> is pretty cool, but others might find it too bloated or complex for their needs. Instead of Docker, we can use <a href=\"https:\/\/linuxcontainers.org\/\" target=\"_blank\" rel=\"noopener\">Linux Containers<\/a>, also known as <code>LXC<\/code>, to do the same thing in a more streamlined, more Linux-y fashion.<\/p>\n<h2><a id=\"Aim_of_this_tutorial_2\"><\/a>Aim of this tutorial<\/h2>\n<p>In this tutorial, we will learn how to run multiple websites on a single VPS with a single public IP address. These websites will all run in their separate LX containers each with its own unique Fully Qualified Domain Name (FQDN). E.g, <code>SUBDOMAIN1.DOMAIN.TLD<\/code> and <code>SUBDOMAIN2.DOMAIN.TLD<\/code>, and so on.<\/p>\n<p>We will achieve this by using one container to act as a load balancer, which will listen on all the requests coming on port 80 or 443 (more on these later) of your VPS. It will read the header information of the incoming request and if it is, say, <code>SUBDOMAIN1.DOMAIN.TLD<\/code>, it will forward the request to the corresponding <em>backend<\/em> container. If it is <code>SUBDOMAIN2.DOMAIN.TLD<\/code>, then the request goes to another container running a different website.<\/p>\n<p>Lastly, we will setup TLS which to encrypt the entire communication between a client computer and your HAProxy container.<\/p>\n<h2><a id=\"Prerequisites_12\"><\/a>Prerequisites<\/h2>\n<ul>\n<li>A registered domain name. We will use a placeholder DOMAIN.TLD where you would use your own domain name instead.<\/li>\n<li>An understanding of DNS records, especially A records and how to set those up.<\/li>\n<li>Root access to a VPS with a static public IP.<\/li>\n<li>Basic understanding of the Linux command line and how to use terminal-based editors like <code>vim<\/code> or <code>nano<\/code>. Use <code>nano<\/code> if you are new to this.<\/li>\n<\/ul>\n<div class=\"cta-inline\"><\/div>\n<h2><a id=\"How_are_websites_resolved_20\"><\/a>How are websites resolved?<\/h2>\n<p>a.k.a. Your DNS setup.<\/p>\n<p>When you visit a website, say <code>EXAMPLE.COM<\/code>, your web browser makes a request to DNS servers, like those belonging to Google (<code>8.8.8.8<\/code>), OpenDNS (<code>208.67.222.222<\/code>), or CloudFlare (<code>1.1.1.1<\/code>). These DNS, or Domain Name Servers, check their records to see what public IP(s) it may point to. The IP is sent back to the browser. Then requests are made to this IP address which would then respond to them, typically, by sending over the contents of a webpage.<\/p>\n<p>On the browser side, you typically expect these web pages to be served over port <code>80<\/code> (for HTTP) or port <code>443<\/code> (for HTTPS) of the server, so the browser sends requests to these particular ports, once it gets an IP address. How are we supposed to run multiple websites if only one or two ports are available to us?<\/p>\n<p>Well, once we have a public IP with our VPS, we can set up multiple A records pointing different names to the same IP. So, if we want to launch websites <code>SUBDOMAIN1.DOMAIN.TLD<\/code> and <code>SUBDOMAIN2.DOMAIN.TLD<\/code> on a single server, both should point to the same IP address. Later on, we will set up a reverse proxy server which would send the traffic coming for <code>SUBDOMAIN1.DOMAIN.TLD<\/code> to one container and <code>SUBDOMAIN2.DOMAIN.TLD<\/code> to another container and so on.<\/p>\n<p>But the important thing is that we will still be listening on port 80 and 443 on our web server, and no other ports, which is what we desire.<\/p>\n<h2><a id=\"Initializing_LXD_and_Creating_LXC_containers_32\"><\/a>Initializing LXD and creating Linux containers<\/h2>\n<p>Let\u2019s start with a clean slate Ubuntu 18.04 LTS server with no additional packages installed or any modifications made on it. Let\u2019s run a customary update and upgrade on it, to make sure we have the latest packages made available to us.<\/p>\n<pre><code>$ sudo apt update\n$ sudo apt upgrade\n<\/code><\/pre>\n<h3><a id=\"LXD_init_41\"><\/a>LXD init<\/h3>\n<p>LXD is the background process (a daemon), and Linux containers (LXC) is the containerization technology behind it. Now we can run <code>lxd init<\/code>, which will ask us several questions and set up the containerization environment for us. It would be better to use OpenZFS to store and manage our container related data.<\/p>\n<pre><code>$ sudo install zfsutils-linux\n$ sudo lxd init\n<\/code><\/pre>\n<p>LXD will ask you a lot of technical questions, and we may not be able to cover all of them in depth. However, let\u2019s stick to a brief description and get our LXC environment up and running.<\/p>\n<ul>\n<li>When it comes to LXD clustering, we will use the default option, which is <code>no<\/code>. Just press &lt;Enter&gt;<\/li>\n<li>New Storage Pool? Again, the default option <code>yes<\/code>.<\/li>\n<li>Name of the storage pool? You can give it a reasonable name like <code>lxc_pool<\/code>, because <code>default<\/code> is not a meaningful name.<\/li>\n<li>Storage backend? Let\u2019s go with <code>zfs<\/code>, the default option.<\/li>\n<li>Say <code>yes<\/code> to creating a new ZFS pool.<\/li>\n<li>With block device, you have an option where you can create a new ZFS pool over an entire block device. If your VPS doesn\u2019t have additional block storage attached to it, go with the default option of <code>no<\/code>.<br \/>\nIf you have selected <code>no<\/code> in the previous step, you would be asked to assign some space in the current file system which LXC will use. Default 15GB is a good starting point for it.<\/li>\n<li>MAAS server connection is not required. Enter <code>no<\/code>.<\/li>\n<li>Local Network Bridge is extremely important for what we are going to do. Answer that with a <code>yes<\/code>.<\/li>\n<li>Let the bridge have the default name <code>lxdbr0<\/code>.<\/li>\n<li>IPv4 addresses? Leave that to the default <code>auto<\/code> as well.<\/li>\n<li>IPv6 addresses are strictly optional. For this tutorial, we are going to say <code>none<\/code> and not use the default value.<\/li>\n<li>Say, <code>no<\/code> to making LXD available over the network.<\/li>\n<li>Automatic update of Cached Images? <code>yes<\/code> of course!<\/li>\n<li>You can print the summary of the entire configuration in the last prompt if you want to. Here\u2019s the output of our configuration for reference.<\/li>\n<\/ul>\n<pre><code>config: {}\ncluster: null\nnetworks:\n- config:\n    ipv4.address: auto\n    ipv6.address: auto\n  description: \"\"\n  managed: false\n  name: lxdbr0\n  type: \"\"\nstorage_pools:\n- config:\n    size: 15GB\n  description: \"\"\n  name: lxc_pool\n  driver: zfs\nprofiles:\n- config: {}\n  description: \"\"\n  devices:\n    eth0:\n      name: eth0\n      nictype: bridged\n      parent: lxdbr0\n      type: nic\n    root:\n      path: \/\n      pool: lxc_pool\n      type: disk\n  name: default\n<\/code><\/pre>\n<p>If you are logged as a user which is not root, add that <code>USER<\/code> to your LXD group.<\/p>\n<pre><code>$ sudo usermod -aG lxd USER\n<\/code><\/pre>\n<p>Now, that we have LXD up and running, we can start creating our containers. LXC containers are different from Docker. You can treat Docker containers as simply a package that you\u2019re installing, in a sense. LXC containers, on the other hand, are treated more like lightweight virtual machines each connected to each other with a private IP address and robust file systems and all the other things you typically associate with a VM.<\/p>\n<h3><a id=\"Creating_containers_109\"><\/a>Creating Linux containers<\/h3>\n<p>We will launch three containers by running:<\/p>\n<pre><code>$ lxc launch ubuntu:18.04 SUBDOMAIN1\n$ lxc launch ubuntu:18.04 SUBDOMAIN2\n$ lxc launch ubuntu:18.04 HAProxy\n<\/code><\/pre>\n<p><code>SUBDOMAIN1<\/code> is just a placeholder. You can name the container anything reasonable like <code>blog<\/code> or <code>portfolio<\/code>. You can see the state of each of them by using the command:<\/p>\n<pre><code>$ lxc list\n+------------+---------+-----------------------+------+------------+-----------+\n|    NAME    |  STATE  |         IPV4          | IPV6 |    TYPE    | SNAPSHOTS |\n+------------+---------+-----------------------+------+------------+-----------+\n| HAProxy    | RUNNING | 10.188.233.252 (eth0) |      | PERSISTENT | 0         |\n+------------+---------+-----------------------+------+------------+-----------+\n| SUBDOMAIN1 | RUNNING | 10.188.233.245 (eth0) |      | PERSISTENT | 0         |\n+------------+---------+-----------------------+------+------------+-----------+\n| SUBDOMAIN2 | RUNNING | 10.188.233.87 (eth0)  |      | PERSISTENT | 0         |\n+------------+---------+-----------------------+------+------------+-----------+\n\n<\/code><\/pre>\n<p>The values above are just examples\u2014your values might differ wildly from these.<\/p>\n<h3><a id=\"IPTable_Rules_137\"><\/a>IPTable Rules<\/h3>\n<p>Since all the incoming traffic (on port 80 and 443) should go through HAProxy first, let\u2019s set some rules to enforce that. First, run the command:<\/p>\n<pre><code>$ ifconfig\n<\/code><\/pre>\n<p>Notice which network interface has the public IP address assigned to it. For example, one of the outputs of <code>ifconfig<\/code> will show your public IP under the <code>inet<\/code> entry. That particular interface\u2019s name can be <code>eth0<\/code> as shown below, or something else depending on your cloud provider. Use that name as your <code>INTERFACE_NAME<\/code>.<\/p>\n<pre><code>eth0: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1500\n        inet PUBLIC_IP_ADDRESS  netmask 255.255.240.0  broadcast\n        inet6 fe80::387f:b5ff:fe8d:5960  prefixlen 64  scopeid 0x20&lt;link&gt;\n        ether 3a:7f:b5:8d:59:60  txqueuelen 1000  (Ethernet)\n        RX packets 1664438  bytes 1952362406 (1.9 GB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 853505  bytes 389343547 (389.3 MB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\n<\/code><\/pre>\n<h2><a id=\"Forwarding_traffic_to_the_HAProxy_container_159\"><\/a>Forwarding traffic to the HAProxy container<\/h2>\n<p>Once that is done, note the IP address of your HAProxy container, as it was shown in <code>lxc list<\/code>. Let\u2019s call it, <code>HAPROXY_IP_ADDRESS<\/code>, and also make a note of your VPS\u2019s public IP address, let\u2019s call it <code>PUBLIC_IP_ADDRESS<\/code>. Now, given these values, run:<\/p>\n<pre><code>$ sudo iptables -t nat -I PREROUTING -i INTERFACE_NAME -p TCP -d PUBLIC_IP_ADDRESS\/32 --dport 80 -j DNAT --to-destination HAPROXY_IP_ADRESS:80\n\n$ sudo iptables -t nat -I PREROUTING -i INTERFACE_NAME -p TCP -d PUBLIC_IP_ADDRESS\/32 --dport 443 -j DNAT --to-destination HAPROXY_IP_ADRESS:443\n<\/code><\/pre>\n<p>We won\u2019t go into the nuances of <code>iptables<\/code> right now\u2014just understand that all the traffic to port 80 and 443 are routed to the HAProxy container. The above settings would reset themselves upon system reboot; we can use the package <code>iptables-persistent<\/code> to fix that:<\/p>\n<pre><code>$ sudo apt-get install iptables-persistent\n<\/code><\/pre>\n<p>For additional security, let\u2019s secure the VPS by configuring the <code>ufw<\/code> firewall. It is essential that you <strong>allow ssh<\/strong> connections before you enable ufw. You might end up locking yourself out of your VPS, otherwise.<\/p>\n<pre><code>$ sudo ufw allow http\n$ sudo ufw allow https\n$ sudo ufw allow ssh\n$ sudo ufw enable\n<\/code><\/pre>\n<h2><a id=\"Configuring_HAProxy_container_184\"><\/a>Configuring HAProxy container<\/h2>\n<p>Login to your HAProxy container by executing the following command:<\/p>\n<pre><code>$ lxc exec HAProxy -- bash\n<\/code><\/pre>\n<p>Now the prompt would change, indicating that you are inside the container, as a root user <code>root@HAProxy:~#<\/code>. We will stay in this environment for the rest of this section. To go back to the main VPS environment, hit <code>Ctrl+D<\/code> or <code>Cmd+D<\/code>.<\/p>\n<p>As the root user, you will want to run <code>apt update<\/code> and then install HAProxy server by running the following:<\/p>\n<pre><code># apt install haproxy\n<\/code><\/pre>\n<p>This command installs and starts the HAProxy server, which is a reverse proxy server. Now, we would like to achieve the following:<\/p>\n<p>Route traffic for <code>SUBDOMAIN1.DOMAIN.TLD<\/code> to <code>SUBDOMAIN1<\/code> container.<br \/>\nLet the container know the client\u2019s IP address so that it can keep track of different visitors to the <code>SUBDOMAIN1.DOMAIN.TLD<\/code>.<br \/>\nDitto for <code>SUBDOMAIN2.DOMAIN.TLD<\/code>.<br \/>\nOptionally, add TLS certificates from Let\u2019s Encrypt.<\/p>\n<p>The configuration file for HAProxy is located at <code>\/etc\/haproxy\/haproxy.cfg<\/code>. It will have two sections <code>global<\/code> and <code>defaults<\/code>. Let\u2019s modify the global section first by adding two lines:<\/p>\n<pre><code>maxconn 2048\ntune.ssl.default-dh-param 2048\n<\/code><\/pre>\n<p>And to the <code>defaults<\/code> sections add:<\/p>\n<pre><code>option  forwardfor\noption  http-server-close\n<\/code><\/pre>\n<p>The result looks something like this:<\/p>\n<pre><code>global\n        log \/dev\/log    local0\n        log \/dev\/log    local1 notice\n        chroot \/var\/lib\/haproxy\n        stats socket \/run\/haproxy\/admin.sock mode 660 level admin expose-fd listeners\n        stats timeout 30s\n        user haproxy\n        group haproxy\n        daemon\n        maxconn 2048\n\n        # Default SSL material locations\n        ca-base \/etc\/ssl\/certs\n        crt-base \/etc\/ssl\/private\n\n        # Default ciphers to use on SSL-enabled listening sockets.\n        # For more information, see ciphers(1SSL). This list is from:\n        #  https:\/\/hynek.me\/articles\/hardening-your-web-servers-ssl-ciphers\/\n        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS\n        ssl-default-bind-options no-sslv3\n        tune.ssl.default-dh-param 2048\n\ndefaults\n        log     global\n        mode    http\n        option  httplog\n        option  dontlognull\n        option  forwardfor\n        option  http-server-close\n        timeout connect 5000\n        timeout client  50000\n        timeout server  50000\n        errorfile 400 \/etc\/haproxy\/errors\/400.http\n        errorfile 403 \/etc\/haproxy\/errors\/403.http\n        errorfile 408 \/etc\/haproxy\/errors\/408.http\n        errorfile 500 \/etc\/haproxy\/errors\/500.http\n        errorfile 502 \/etc\/haproxy\/errors\/502.http\n        errorfile 503 \/etc\/haproxy\/errors\/503.http\n        errorfile 504 \/etc\/haproxy\/errors\/504.http\n\n<\/code><\/pre>\n<p>The <code>maxconn<\/code> parameter need not be <code>2048<\/code>, but any value that you might deem fit as the maximum number of simultaneous connections. The <code>forwardfor<\/code> option is to retain the real source IP. The websites you have hosted can keep track of real-world visitors. Otherwise, it may appear that only the reverse proxy server has ever visited your websites <code>SUBDOMAIN1.DOMAIN.TLD<\/code> and <code>SUBDOMAIN2.DOMAIN.TLD<\/code>.<\/p>\n<p>Now we need to add a frontend section at the bottom of the file, which will tell HAProxy how to filter the incoming requests. Then we will add a couple of backend sections telling haproxy where the filtered requests would go:<\/p>\n<pre><code>frontend http_frontend\n    bind *:80\n\n    acl web_host1 hdr(host) -i SUBDOMAIN1.DOMAIN.TLD\n    acl web_host2 hdr(host) -i SUBDOMAIN2.DOMAIN.TLD\n\n    use_backend subdomain1 if web_host1\n    use_backend subdomain2 if web_host2\n\nbackend subdomain1\n    balance leastconn\n    http-request set-header X-Client-IP %[src]\n    server SUBDOMAIN1 SUBDOMAIN1.lxd:80 check\n\nbackend subdomain2\n    balance leastconn\n    http-request set-header X-Client-IP %[src]\n    server SUBDOMAIN2 SUBDOMAIN2.lxd:80 check\n<\/code><\/pre>\n<p>The above text is what you should append to the <code>\/etc\/haproxy\/haproxy.cfg<\/code> file if you are interested in using just HTTP, without SSL. The frontend reads header information <code>hdr(host) -i ...<\/code> and distributes traffic accordingly.<\/p>\n<p>The backend forwards the source IP <code>set-header X-Client-IP %[SRC]<\/code> and refers to the lxc containers using local domain names, like <code>SUBDOMAIN1.lxd<\/code>. This is a useful feature of LXD which we can use to our advantage. It is completely separate from the actual <code>SUBDOMAIN1.DOMAIN.TLD<\/code> that you would use to view your websites.<\/p>\n<p>You can now check if the configuration file is okay or not by running:<\/p>\n<pre><code># \/usr\/sbin\/haproxy -f \/etc\/haproxy\/haproxy.cfg -c\n<\/code><\/pre>\n<p>If everything checks out, restart HAProxy:<\/p>\n<pre><code># service haproxy reload\n<\/code><\/pre>\n<p>You can now safely exit this container by running <code>exit<\/code> command.<\/p>\n<h2><a id=\"Running_multiple_websites_309\"><\/a>Running multiple websites<\/h2>\n<p>Now you can login to the backend container named <code>SUBDOMAIN1<\/code> and <code>SUBDOMAIN2<\/code>. We will install Nginx in one and Apache web server in other to see how they work.<\/p>\n<pre><code>$ lxc exec SUBDOMAIN1 -- bash\n# apt install apache2\n# exit\n<\/code><\/pre>\n<pre><code>$ lxc exec SUBDOMAIN2 -- bash\n# apt install nginx\n# exit\n<\/code><\/pre>\n<p>Now if you visit <code>SUBDOMAIN1.DOMAIN.TLD<\/code> from your web browser, you will see that it is running Apache2 web server. Visiting <code>SUBDOMAIN2.DOMAIN.TLD<\/code> will show you an Nginx landing page instead!<\/p>\n<p>You now have two different websites hosted on a single VPS. Login to their respective containers to install <a href=\"https:\/\/www.ssdnodes.com\/blog\/tutorial-installing-easyengine-and-building-lets-encrypt-enabled-sites\/\">WordPress<\/a>, <a href=\"https:\/\/www.ssdnodes.com\/blog\/ghost-cms-tutorial-ubuntu\/\">Ghost<\/a>, or <a href=\"https:\/\/www.ssdnodes.com\/blog\/10-flat-file-content-managers-to-help-you-ditch-wordpress\/\">another CMS<\/a>, if you\u2019re starting a blog. You can install anything you\u2019d install on a barebones Linux VPS, such as <a href=\"https:\/\/www.ssdnodes.com\/blog\/ultimate-guide-self-hosted-alternatives-to-cloud-apps\/\">self-hosted web apps<\/a>, too.<\/p>\n<h2><a id=\"Installing_Lets_Encrypt_certificates_329\"><\/a>Installing Let\u2019s Encrypt certificates<\/h2>\n<p>We haven\u2019t added SSL certificates, yet. If you are using Cloudflare as your DNS, you can enable SSL over there which would work just fine. It is free and easy to install. It will get your websites the green padlock symbol where the URL appears, and you won\u2019t have to worry about certificate renewal.<\/p>\n<p>If you want to use Let\u2019s Encrypt for a free SSL certificate instead, you would have to jump through a couple of hoops. Let\u2019s log back into the HAProxy container: <code>lxc exec HAProxy -- bash<\/code>.<\/p>\n<h3><a id=\"Obtaining_certs_335\"><\/a>Obtaining certs<\/h3>\n<p>First, disable the HAProxy service so we can get started with certificate installation. Add the <a href=\"https:\/\/certbot.eff.org\/\" target=\"_blank\" rel=\"noopener\">Certbot<\/a> PPA to your list of trusted repositories and then install Certbot, which will fetch certificates for us.<\/p>\n<pre><code># service haproxy stop\n# add-apt-repository ppa:certbot\/certbot\n# apt update\n# apt install certbot\n<\/code><\/pre>\n<p>Obtain the certificates by running <code>certbot certonly<\/code>. This command will ask you several questions, including the domain names you want to have certified.<\/p>\n<pre><code># certbot certonly\nHow would you like to authenticate with the ACME CA?\n-------------------------------------------------------------------------------\n1: Spin up a temporary web server (standalone)\n2: Place files in webroot directory (webroot)\n-------------------------------------------------------------------------------\nSelect the appropriate number [1-2] then [enter] (press 'c' to cancel):\n<\/code><\/pre>\n<p>Select <strong>1<\/strong>. Then it would ask you for your email, enter it and <strong>Agree<\/strong> to terms and conditions. The command will also ask if you\u2019re willing to share your email address with <a href=\"http:\/\/eff.org\" target=\"_blank\" rel=\"noopener\">eff.org<\/a>. You can opt out of it. Then, most importantly, you will be asked for your domain name(s). Enter your registered domain names. For example, we will enter <code>SUBDOMAIN1.DOMAIN.TLD, SUBDOMAIN2.DOMAIN.TLD<\/code> separated by a comma and a space.<\/p>\n<h3><a id=\"Configuring_HAProxy_to_serve_SSL_360\"><\/a>Configuring HAProxy to serve SSL<\/h3>\n<p>If successful, it would tell you where the certs are stored. Typically the certificates are saved at <code>\/etc\/letsencrypt\/live\/SUDOMAIN1.DOMAIN.TLD<\/code> directory, which is named after the first FQDN you entered while obtaining the certificates.<\/p>\n<p>We want to combine two files, the <code>fullchain.pem<\/code> and <code>privkey.pem<\/code> files, into a single file in a directory <code>\/etc\/haproxy\/certs<\/code>.<\/p>\n<pre><code>$ mkdir -p \/etc\/haproxy\/certs\n$ cat \/etc\/letsencrypt\/live\/SUBDOMAIN1.DOMAIN.TLD\/fullchain.pem \/etc\/letsencrypt\/live\/SUBDOMAIN1.DOMAIN.TLD\/privkey.pem &gt; \/etc\/haproxy\/certs\/SUBDOMAIN1.DOMAIN.TLD.pem\n<\/code><\/pre>\n<p>Next we must revisit our <code>\/etc\/haproxy\/haproxy.cfg<\/code> file and add an SSL frontend to it, as well as modify the backend to redirect all non-SSL requests to SSL. We will add the SSL frontend first.<\/p>\n<pre><code>frontend www-https\n    bind *:443 ssl crt \/etc\/haproxy\/certs\/SUBDOMAIN1.DOMAIN.TLD.pem\n    reqadd X-Forwarded-Proto:\\ https\n\n    acl host_web1 hdr(host) -i SUBDOMAIN1.DOMAIN.TLD\n    acl host_web2 hdr(host) -i SUBDOMAIN2.DOMAIN.TLD\n\n    use_backend subdomain1 if host_web1\n    use_backend subdomain2 if host_web2\n<\/code><\/pre>\n<p>Next, we will add the line <code>redirect scheme https if !{ ssl_fc }<\/code> in each of our backend section to redirect requests to SSL. Like so:<\/p>\n<pre><code>backend subdomain1\n    balance leastconn\n    http-request set-header X-Client-IP %[src]\n    redirect scheme https if !{ ssl_fc }\n    server SUBDOMAIN1 SUBDOMAIN1.lxd:80 check\n\n<\/code><\/pre>\n<p>The final result would look something like this:<\/p>\n<pre><code>frontend www-https\n    bind *:443 ssl crt \/etc\/haproxy\/certs\/SUBDOMAIN1.DOMAIN.TLD.pem\n    reqadd X-Forwarded-Proto:\\ https\n\n    acl host_web1 hdr(host) -i SUBDOMAIN1.DOMAIN.TLD\n    acl host_web2 hdr(host) -i SUBDOMAIN2.DOMAIN.TLD\n\n    use_backend subdomain1 if host_web1\n    use_backend subdomain2 if host_web2\n\nfrontend http_frontend\n    bind *:80\n\n    acl web_host1 hdr(host) -i SUBDOMAIN1.DOMAIN.TLD\n    acl web_host2 hdr(host) -i SUBDOMAIN2.DOMAIN.TLD\n\n    use_backend subdomain1 if web_host1\n    use_backend subdomain2 if web_host2\n\nbackend subdomain1\n    balance leastconn\n    http-request set-header X-Client-IP %[src]\n    redirect scheme https if !{ ssl_fc }\n    server SUBDOMAIN1 SUBDOMAIN1.lxd:80 check\n\nbackend subdomain2\n    balance leastconn\n    http-request set-header X-Client-IP %[src]\n    redirect scheme https if !{ ssl_fc }\n    server SUBDOMAIN2 SUBDOMAIN2.lxd:80 check\n<\/code><\/pre>\n<p>And you can now you can run <code>service haproxy reload<\/code> for the new configurations to take effect. Check if SSL is working or not by visiting your FQDNs from a web browser. You will see a secure symbol at the URL bar if everything as worked out fine.<\/p>\n<h3><a id=\"Renewing_certificates_433\"><\/a>Renewing certificates<\/h3>\n<p>Your certs expire every 90 days, and you would need to renew them accordingly. While this process can be automated, let\u2019s look at a manual way to do this for the sake of simplicity.<\/p>\n<ul>\n<li>Login to the HAProxy container: <code>lxc exec HAProxy -- bash<\/code>.<\/li>\n<li>Stop the service: <code>service haproxy stop<\/code>.<\/li>\n<li>Run: <code>certbot renew<\/code>.<\/li>\n<li>Remove the older certificate from HAProxy configs <code>rm \/etc\/haproxy\/certs\/SUBDOMAIN1.DOMAIN.TLD.pem<\/code>.<\/li>\n<li>Add the newer certs: <code>cat<br \/>\n\/etc\/letsencrypt\/live\/SUBDOMAIN1.DOMAIN.TLD\/fullchain.pem<br \/>\n\/etc\/letsencrypt\/live\/SUBDOMAIN1.DOMAIN.TLD\/privkey.pem &gt;<br \/>\n\/etc\/haproxy\/certs\/SUBDOMAIN1.DOMAIN.TLD.pem<\/code>.<code><\/code><\/li>\n<li>Restart HAProxy: <code>service haproxy start<\/code>.<\/li>\n<\/ul>\n<h2><a id=\"Conclusion_444\"><\/a>Conclusion<\/h2>\n<p>The overall setup covered here is quite complicated and can appear frustrating at times. If you feel that way, it\u2019s okay! Knowing what you have done in every step of the way can teach you a lot about how these systems work and how you can optimally use them.<\/p>\n<p>If you have any doubts, queries or corrections, feel free to drop a comment below.<\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Instead of relying on Docker to host multiple websites on a single VPS, use native Linux containers (LXC) and a tiny reverse proxy for lightning speed.<\/p>\n","protected":false},"author":20,"featured_media":2421,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[18,30],"tags":[183,262],"class_list":["post-2420","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-tutorials","tag-containers","tag-haproxy"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2420","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/comments?post=2420"}],"version-history":[{"count":3,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2420\/revisions"}],"predecessor-version":[{"id":13013,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/2420\/revisions\/13013"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media\/2421"}],"wp:attachment":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media?parent=2420"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/categories?post=2420"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/tags?post=2420"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}