HAProxy

Install haproxy from stretch-backports, or you may encounter problems.

Base configuration

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

        # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy-1.8.0&openssl=1.1.0i&hsts=yes&profile=modern
        # If you are using different version (check with `openssl version` and `haproxy -v`, go get new ciphers&options)
        # set default parameters to the intermediate configuration
        ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
        ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
        ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
        ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

defaults
#       enables tcplog so disabled
#       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

Force HTTPS

# HTTP (port 80)
frontend http-in
        bind :::80 v4v6
        reqadd X-Forwarded-Proto:\ http

        use_backend letsencrypt if { path_beg -i /.well-known/acme-challenge }

        default_backend redirect-to-https

backend redirect-to-https
        redirect scheme https if !{ ssl_fc }

backend letsencrypt
        server letsencrypt-http 127.0.0.1:12345 verify none

Let’s Encrypt

Use this utility to convert certs outputted by Certbot to ones compatible by HAProxy.

sudo su -

mkdir /etc/haproxy/certs
chmod 700 /etc/haproxy/certs

git clone https://github.com/theel0ja/haproxy-scripts.git
ln -s ~/haproxy-scripts/le-combiner/le-combiner.sh ~/le-combiner.sh

./le-combiner.sh

To generate certificates, see this guide.

Cronjob (root)

0       12      */3     *       *       /root/haproxy-scripts/le-combiner/le-combiner.sh
30       12      */3     *       *       cd /root/haproxy-scripts/ && git pull --quiet

User-Agent Blocklist

sudo mkdir -p /etc/haproxy/useragent-blocklist/
sudo chown -R $USER:$USER /etc/haproxy/useragent-blocklist/
git clone https://git.lelux.fi/theel0ja/useragent-blocklist /etc/haproxy/useragent-blocklist/

Haproxy configuration for HTTPS

# HTTPS (port 443)
frontend https-in
        bind :::443 v4v6 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        reqadd X-Forwarded-Proto:\ https

        http-response set-header Strict-Transport-Security max-age=31536000

        ...

        # Bad bots
        # https://haproxy-dl.lelux.fi/blocked-ua.lst
        acl bad_ua req.fhdr(User-Agent) -i -f /etc/haproxy/useragent-blocklist/blocked-ua.lst
        use_backend bad_guy if bad_ua !{ path /robots.txt }

        ...

backend bad_guy
        mode http
        errorfile 503 /etc/haproxy/useragent-blocklist/haproxy-error.html

Should return 403

curl https://YOUR_SERVER/example.html --header "User-Agent: YisouSpider" -I

Should return 200 (assuming it normally would)

curl https://YOUR_SERVER/robots.txt --header "User-Agent: YisouSpider" -I

Cronjob (with user that has permissions to /etc/haproxy/useragent-blocklist/)

0       */3     *       *       *       cd /etc/haproxy/useragent-blocklist && git pull --quiet

Nginx backends

Notice to Debian 9 (stretch) users: nginx 1.10.3-1+deb9u2 from stretch doesn’t seem to work. Please use 1.14.1-1~bpo9+1 from stretch-backports.

/etc/haproxy/haproxy.cfg

frontend https-in
        ...

        use_backend web1 if { hdr(host) -i www.example.com }

# Backends

...

backend web1
        server web1 SERVER_IP:80 send-proxy-v2

/etc/nginx/sites-available/www.example.com

server {
        listen 80 proxy_protocol;
#        listen [::]:80 proxy_protocol;

        server_name www.example.com;

        root /var/www/www.example.com;

        # Add index.php to the list if you are using PHP
        index index.html;

        # include /etc/nginx/snippets/useragent-blocklist/nginx.conf;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        access_log off;
        error_log off; # /var/log/nginx/www.example.com.error.log;
}

/etc/nginx/conf.d/real-ip.conf

set_real_ip_from HAPROXY_IP;
real_ip_header proxy_protocol;

Check configuration

sudo haproxy -c -- /etc/haproxy/haproxy.cfg

DNS-over-TLS

listen dns
        bind :::853 v4v6 tfo ssl crt /etc/haproxy/certs
        mode tcp
        server resolver 127.0.0.1:53

You may replace 127.0.0.1:53 with address of your resolver. I recommend using Unbound as resolver.

Try with the following command:

export SERVER_HOSTNAME="resolver2.lelux.fi" && kdig -d @$SERVER_HOSTNAME +tls-ca +tls-host=$SERVER_HOSTNAME +tls-sni=$SERVER_HOSTNAME whoami.v4.powerdns.org

Replace resolver2.lelux.fi with your resolver address. You may replace @$SERVER_HOSTNAME with @ipaddress (replace ipaddress).

It should return your DNS resolver’s outgoing IP address as result.

Example result:

$ export SERVER_HOSTNAME="resolver2.lelux.fi" && kdig -d @$SERVER_HOSTNAME +tls-ca +tls-host=$SERVER_HOSTNAME +tls-sni=$SERVER_HOSTNAME whoami.v4.powerdns.org
;; DEBUG: Querying for owner(whoami.v4.powerdns.org.), class(1), type(1), server(resolver2.lelux.fi), port(853), protocol(TCP)
;; DEBUG: TLS, imported 154 system certificates
;; DEBUG: TLS, received certificate hierarchy:
;; DEBUG:  #1, CN=resolver2.lelux.fi
;; DEBUG:      SHA-256 PIN: bC5Vi0G8FqvFrf9TgNtJomNLDxG58tEj/mlWX6oOt+E=
;; DEBUG:  #2, C=US,O=Let's Encrypt,CN=Let's Encrypt Authority X3
;; DEBUG:      SHA-256 PIN: YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=
;; DEBUG: TLS, skipping certificate PIN check
;; DEBUG: TLS, The certificate is trusted. 
;; TLS session (TLS1.2)-(ECDHE-SECP256R1)-(RSA-SHA512)-(AES-256-GCM)
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 10983
;; Flags: qr rd ra; QUERY: 1; ANSWER: 1; AUTHORITY: 0; ADDITIONAL: 1

;; EDNS PSEUDOSECTION:
;; Version: 0; flags: ; UDP size: 4096 B; ext-rcode: NOERROR

;; QUESTION SECTION:
;; whoami.v4.powerdns.org.		IN	A

;; ANSWER SECTION:
whoami.v4.powerdns.org.	60	IN	A	104.244.79.229

;; Received 67 B
;; Time 2019-06-25 05:24:15 EEST
;; From 104.244.79.229@853(TCP) in 264.6 ms

Further reading