HAProxy: Using HAProxy for SSL termination on Ubuntu

HAProxy is a high performance TCP/HTTP (Level 4 and Level 7) load balancer and reverse proxy.  A common pattern is allowing HAProxy to be the fronting SSL-termination point, and then HAProxy determines which pooled backend server serves the request.

Installation from Repository

Although HAProxy is in the main Ubuntu repository, the version is 1.4 and we really need access to the newer feature set.   The configuration files in this article are aimed at HAProxy >= 1.6.

You have a couple of options for apt-get installation.  The Debian HAProxy packaging team offers these packages, but I prefer Vincent Bernat’s PPA.   Feel free to go whichever way you feel comfortable, below are the steps I use for 1.7:

# apt-cache policy haproxy
# apt-get install python-software-properties software-properties-common -y
# add-apt-repository ppa:vbernat/haproxy-1.7
# apt-get update
# apt-cache policy haproxy
# apt-get install haproxy -y
# haproxy -v

Logging

HAProxy sends log messages via rsyslog, so we must make sure this is configured properly.  The installation created a config file ‘/etc/rsyslog.d/49-haproxy.conf’ which will route the files to ‘/var/log/haproxy.log’.

Modify ‘/etc/rsyslog.conf’ so that it is listening on UDP port 514:

$ModLoad imudp 
$UDPServerRun 514 
$UDPServerAddress 127.0.0.1

And then restart the rsyslog service:

# service rsyslog restart

Secure Certificate

If you are going to use HAProxy as an SSL termination point, then it needs a private/public key pair.  The easiest way to satisfy this requirement is to create a self-signed certificate as described in the article I wrote here.

You can also create a self-signed SAN certificate.

Open Firewall Ports

We need to make sure port 80 and 443 are open.  And then the HAProxy monitoring port on 8998.

# ufw allow 80/tcp
# ufw allow 443/tcp
# ufw allow 8998/tcp

SSL Termination

The file ‘/etc/haproxy/haproxy.conf’ is the configuration file that defines all options and frontend/backends.  Place the contents below into the file:

#------------------
# Global settings
#------------------
global
    log /dev/log    local0 debug
    log /dev/log    local1 notice
    user haproxy
    group haproxy
    chroot  /var/lib/haproxy
    daemon
    debug
    stats socket /var/lib/haproxy/stats mode 660 level admin
    maxconn 2000
    tune.ssl.default-dh-param   2048
    ssl-default-bind-ciphers    ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
    ssl-default-bind-options    no-sslv3 no-tlsv10
    # no-tlsv11

#------------------
# common defaults that all the 'listen' and 'backend' sections will
# use- if not designated in their block
#------------------
defaults
    log global
    mode http
    retries 3
    option      http-keep-alive
    option      httplog
    option      dontlognull
    option      forwardfor
    option      http-server-close
    log-format %ci:%cp\ [%t]\ %ft\ %b/%s\ %Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ %CS\ %tsc\ %ac/%fc/%bc/%sc/%rc\ %sq/%bq\ %hr\ %hs\ %{+Q}r
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    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

#------------------
# internal statistics
#------------------
listen stats
    bind 0.0.0.0:8998
    mode http
    stats auth Admin:password
    stats enable
    stats realm Haproxy\ Statistics
    stats refresh 20s
    stats uri /admin?stats

#------------------
# frontend instances
#------------------
frontend unsecured
    bind        *:80
    reqadd      X-Forwarded-Proto:\ https
    redirect    scheme https code 301 if !{ ssl_fc }
    default_backend www-backend

frontend www-https
    bind        *:443 ssl crt /etc/pki/tls/certs/FQDN.pem
    log-format %ci:%cp\ [%t]\ %ft\ %b/%s\ %Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ %CS\ %tsc\ %ac/%fc/%bc/%sc/%rc\ %sq/%bq\ %hr\ %hs\ %{+Q}r\ ssl_version:%sslv\ ssl_cipher:%sslc
    # passing on that browser is using https
    reqadd X-Forwarded-Proto:\ https
    # for Clickjacking
    rspadd X-Frame-Options:\ SAMEORIGIN
    # prevent browser from using non-secure
    rspadd Strict-Transport-Security:\ max-age=15768000
    # use to send simple file response
    errorfile 503 /etc/haproxy/200.http
    # use real backend later
    #default_backend www-backend 


#------------------
# backend instances
#------------------
backend www-backend
    balance roundrobin
    cookie WWWID insert indirect nocache
    redirect scheme https if !{ ssl_fc }
    server mypool1 127.0.0.1:8080 cookie www1 check inter 3000
    #server mypool2 127.0.0.1:8080 cookie www2 check inter 3000
    #server mypool3 127.0.0.1:8080 cookie www3 check inter 3000

You need to replace ‘FQDN’ with the actual fully qualified domain name of your Nginx host for the certificate .pem name.

So that you don’t have to setup a pool of backing servers right now, create “/etc/haproxy/200.http” to serve a simple static file when accessed.

HTTP/1.0 200 Found
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html>
 <head>
 <title>Up</title>
 </head>
 <body>
 <p>Up</p>
 </body>
 </html>

Later on, when your real backing application servers are running, you should remove the ‘errorfile’ line, and uncomment “default_backend www-backend” so real content is delivered.

All non-secure requests are redirected to HTTPS for security.

The monitoring statistics can be reached at http://<haproxy>:8998/admin?stats

Start Service

To start HAProxy, use the command below.  Logs can be found at ‘/var/log/haproxy.log’.

# service haproxy start

Be sure that when you pull up the HAProxy server in the browser, you use the fully qualified host name (and not the IP).  If you have to make changes to your local hosts file, then do so, because the browser address needs to match the CN/SAN in the certificate.

Start your research here and here on how to harden HAproxy security.

 

REFERENCES

http://www.haproxy.org/

https://launchpad.net/~vbernat/+archive/ubuntu/haproxy-1.7

https://haproxy.debian.net

http://serverfault.com/questions/645924/haproxy-logging-to-syslog

https://serversforhackers.com/load-balancing-with-haproxy

https://www.haproxy.com/doc/aloha/7.0/haproxy/healthchecks.html

http://blog.haproxy.com/2012/03/29/load-balancing-affinity-persistence-sticky-sessions-what-you-need-to-know/

 

NOTES

Create “/etc/haproxy/200.http” to serve simple static file from front end

HTTP/1.0 200 Found
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<html>
 <head>
 <title>Up</title>
 </head>
 <body>
 <p>Up</p>
 </body>
 </html>