diff --git a/haproxy-sni/.gitignore b/haproxy-sni/.gitignore
new file mode 100644
index 0000000..2232829
--- /dev/null
+++ b/haproxy-sni/.gitignore
@@ -0,0 +1 @@
+*.dump
diff --git a/haproxy-sni/README.md b/haproxy-sni/README.md
new file mode 100644
index 0000000..9d7de21
--- /dev/null
+++ b/haproxy-sni/README.md
@@ -0,0 +1,20 @@
+# HAProxy with SNI & Host-based rules
+
+This is a proof of concept, enabling HAProxy to use *either* SNI to redirect to backends with their own HTTPS certificates (which are then fully exposed to the client; HAProxy only proxies on a TCP level in that case), *as well as* to terminate HTTPS and use the Host header to redirect to backends that use HTTP (or a new HTTPS connection).
+
+## How it works
+1. The `http_redirect_frontend` is only there to listen on port 80 and redirect every request to HTTPS.
+2. The `https_sni_frontend` listens on port 443 and chooses a backend based on the SNI hostname of the TLS connection.
+3. The `https_termination_backend` passes all requests to a unix socket (using the plain TCP data).
+4. The `https_termination_frontend` listens on said unix socket, terminates the HTTPS connections and then chooses a backend based on the Host header.
+
+In the example (see [haproxy.cfg](haproxy.cfg)), the `pages_backend` is listening via HTTPS and is providing its own HTTPS certificates, while the `gitea_backend` only provides HTTP.
+
+## How to test
+```bash
+docker-compose up -d
+./test.sh
+
+# For manual testing: all HTTPS URLs connect to localhost:443 & certificates are not verified.
+./test.sh [curl-options...] <url>
+```
diff --git a/haproxy-sni/dhparam.pem b/haproxy-sni/dhparam.pem
new file mode 100644
index 0000000..088f967
--- /dev/null
+++ b/haproxy-sni/dhparam.pem
@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
+87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
+YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
+7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
+ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
+-----END DH PARAMETERS-----
\ No newline at end of file
diff --git a/haproxy-sni/docker-compose.yml b/haproxy-sni/docker-compose.yml
new file mode 100644
index 0000000..4dd8677
--- /dev/null
+++ b/haproxy-sni/docker-compose.yml
@@ -0,0 +1,22 @@
+version: "3"
+services:
+  haproxy:
+    image: haproxy
+    ports: ["443:443"]
+    volumes:
+    - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
+    - ./dhparam.pem:/etc/ssl/dhparam.pem:ro
+    - ./haproxy-certificates:/etc/ssl/private/haproxy:ro
+    cap_add:
+    - NET_ADMIN
+  gitea:
+    image: caddy
+    volumes:
+    - ./gitea-www:/srv:ro
+    - ./gitea.Caddyfile:/etc/caddy/Caddyfile:ro
+  pages:
+    image: caddy
+    volumes:
+    - ./pages-www:/srv:ro
+    - ./pages.Caddyfile:/etc/caddy/Caddyfile:ro
+    
diff --git a/haproxy-sni/gitea-www/index.html b/haproxy-sni/gitea-www/index.html
new file mode 100644
index 0000000..d092750
--- /dev/null
+++ b/haproxy-sni/gitea-www/index.html
@@ -0,0 +1 @@
+Hello to Gitea!
diff --git a/haproxy-sni/gitea.Caddyfile b/haproxy-sni/gitea.Caddyfile
new file mode 100644
index 0000000..e92a157
--- /dev/null
+++ b/haproxy-sni/gitea.Caddyfile
@@ -0,0 +1,3 @@
+http://codeberg.org
+
+file_server
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem b/haproxy-sni/haproxy-certificates/codeberg.org.pem
new file mode 100644
index 0000000..e85b673
--- /dev/null
+++ b/haproxy-sni/haproxy-certificates/codeberg.org.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEUDCCArigAwIBAgIRAMq3iwF963VGkzXFpbrpAtkwDQYJKoZIhvcNAQELBQAw
+gYkxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEvMC0GA1UECwwmbW9t
+YXJAbW9yaXR6LWxhcHRvcCAoTW9yaXR6IE1hcnF1YXJkdCkxNjA0BgNVBAMMLW1r
+Y2VydCBtb21hckBtb3JpdHotbGFwdG9wIChNb3JpdHogTWFycXVhcmR0KTAeFw0y
+MTA2MDYwOTQ4NDFaFw0yMzA5MDYwOTQ4NDFaMFoxJzAlBgNVBAoTHm1rY2VydCBk
+ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEvMC0GA1UECwwmbW9tYXJAbW9yaXR6LWxh
+cHRvcCAoTW9yaXR6IE1hcnF1YXJkdCkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCrSPSPM6grNZMG4ZKFCVxuXu+qkHdzSR96QUxi00VkIrkGPmyMN7q7
+rUQJto9C9guJio3n7y3Bvr5kBjICjyWQd7GfkVuYgiYiG/O2hy1u1dIMCAB/Zhx1
+F1mvRfn/Q4eZk2GSOUM+kC0xaNsn2827VGLOGFywUhRmu7J9QSQ3x1Pi5BME7eNC
+AKup0CbrMrZSzKAEuYujLY0UYRxUrguMnV60wxJDCYE14YDxn9t0g7wQmzyndupk
+AMLNJZX5L83RA6vUEuTVYBFcyB0Fu3oBLQ31y5QOZ7WF/QiO5cPicQJI/oyXlHq4
+97BWS/H28kj1H5ZM8+5yhCYDtgj7dERpAgMBAAGjYTBfMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSOSXQZqt2gjbTOkE9Q
+ddI8SYPqrDAXBgNVHREEEDAOggxjb2RlYmVyZy5vcmcwDQYJKoZIhvcNAQELBQAD
+ggGBAJ/57DGqfuOa3aS/nLeAzl8komvyHuoOZi9yDK2Jqr+COxP58zSu8xwhiZfc
+TJvIyB9QR7imGiQ7fEKby40q8uxGGx13oY7gQy7PG8hHk2dkfDZuSQacnpPRC3W0
+0dL2CQIog6rw6jJHjxneitkX9FUmOnHIKy7LHya0Sthg36Z0Qw5JA3SCy6OQNepR
+R2XzwTZ0KFk6gAuKCto8ENUlU5lV9PM4X3U0cBOIc5LJAPM+cxEDUocFtFqKJPbe
+YYlSeB200YhYOdi+x34n9xnQjFu/jVlWF+Y0tMBB1WWq6rZbnuylwWLYQZAo10Co
+D3oWsYRlD/ZL7X20ztIy8vRXz33ugnxxf88Q7csWDYb4S325svLfI2EjciIxYmBo
+dSJxXRQkadjIoI7gNvzeWBkYSJpQUbaD4nT2xRS8vfuv42/DrIehb8SbTivHmcB3
+OibpWIvDtS1B8thIlzl0edb+8pb6mof7pOBxoZdcBsSAk2/48s+jfRHfD9XcuKnv
+hGCdSQ==
+-----END CERTIFICATE-----
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem.key b/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
new file mode 100644
index 0000000..b9c4d61
--- /dev/null
+++ b/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrSPSPM6grNZMG
+4ZKFCVxuXu+qkHdzSR96QUxi00VkIrkGPmyMN7q7rUQJto9C9guJio3n7y3Bvr5k
+BjICjyWQd7GfkVuYgiYiG/O2hy1u1dIMCAB/Zhx1F1mvRfn/Q4eZk2GSOUM+kC0x
+aNsn2827VGLOGFywUhRmu7J9QSQ3x1Pi5BME7eNCAKup0CbrMrZSzKAEuYujLY0U
+YRxUrguMnV60wxJDCYE14YDxn9t0g7wQmzyndupkAMLNJZX5L83RA6vUEuTVYBFc
+yB0Fu3oBLQ31y5QOZ7WF/QiO5cPicQJI/oyXlHq497BWS/H28kj1H5ZM8+5yhCYD
+tgj7dERpAgMBAAECggEAAeW+/88cr83aIRtimiKuaXKXyRXsnNRUivAqPnYEsMVJ
+s24BmdQMN4QF2u2wzJcZLZ7hT45wvVK1nToMV8bqLZ2F1DSyBRB8B6iznHQG5tFr
+kEKObtrcuddWYQCvckp3OBZP4GTN/+Vs+r0koF5o+whGR+4xKKrgGvs9UPHlytBf
+0DMzAzWzGPp6qBPw2sUx/fa9r5TqFW+p4SEOZJUqL2/zEZ6KBWbKw5T1e1y2kMEc
+cquUQ4avqK/N1nwRNKUnTvW827v0k7HQ2cFdrjIATNlICslOWJQicG5GUOuSBkTC
+0FFkSTtHP4qm0BqShjv6NDmzX+3WCVkGOKFOI+zuWQKBgQDBq8yEcvfMJY98KNlR
+eKKdJAMJvKdoD65Yv6EG7ZzpeEWHaTGhu71RPgHYkHn8h1T/9WniroSk19+zb4lP
+mMsBwxpg5HejWPzIiiJRkRCRA7aZZfvaXfIWryB4kI1tlGHBNN/+SYpG1zdNumtp
+Xyb/sQWMMWRZdRgclF8V+NvduwKBgQDiaM59gBROleREduFZE1a0oXtt+CrwrPlz
+hclrkYl1FbTA4TdL4JNbj5jCXCR8YakFhxWEmhwq+Dgl1NQY/YjHyG3w2imaeASX
+QUsEvAIvNrv1mIELiYCLmUElyX4WL3UhqveOFcZUvR1Z4TTwruPQmXf6BJEBLbWI
+f7odmG6yKwKBgQCzpuLjZiZY9+qe2OGmQopNzE8JJDgCPrGS38fGvnnU1N1iXAFP
+LvDRwPxDYNnXl84QVR2wygR/SUTYlTlBXdHKw6nfgW89Vlm+yOxGz5MXgeNLbp/u
+k0DzK+aqECUxJfh8GclCgANF7XP+pVPn/f0WKKalwld86DLCqBuALUX+6wKBgCUh
+gxvZ8Xqh4nnH9VUicsnU4eU7Ge+2roJfopTdnWlyUd6AEQ2EmyYc+rSFYAZ2Db42
+VTUWASCa7LpnmREwI0qAeGdToBcRL8+OibsRClqr409331IBDu/WBnUoAmGpDtCi
+tU68C3bCPRoMcR430GzZfm+maBGFaYwlRmSsJxtZAoGADSA3uAZBuWNDPNKUas2k
+Z2dXFEPNpViMjQzJ+Ko7lbOBpUUUQfZF2VMSK4lcnhhbmhcMrYzWWmh6uaw78aHY
+e3M//BfcVMdxHw7EemGOViNNq3uDIwzvYteoe6fAOA7MaV+WjJaf+smceR4o38fk
+U9RTkKpRJIcvEW5bvTI9h4o=
+-----END PRIVATE KEY-----
diff --git a/haproxy-sni/haproxy.cfg b/haproxy-sni/haproxy.cfg
new file mode 100644
index 0000000..869bae3
--- /dev/null
+++ b/haproxy-sni/haproxy.cfg
@@ -0,0 +1,98 @@
+#####################################
+## Global Configuration & Defaults ##
+#####################################
+
+global
+  log stderr format iso local7
+
+  # generated 2021-06-05, Mozilla Guideline v5.6, HAProxy 2.1, OpenSSL 1.1.1d, intermediate configuration
+  # https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.1d&guideline=5.6
+  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 prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
+
+  ssl-default-server-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-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
+  ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
+
+  # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
+  ssl-dh-param-file /etc/ssl/dhparam.pem
+
+defaults
+  log global
+  timeout connect 30000
+  timeout check 300000
+  timeout client 300000
+  timeout server 300000
+
+############################################################################
+## Frontends: HTTP; HTTPS → HTTPS SNI-based; HTTPS → HTTP(S) header-based ##
+############################################################################
+
+frontend http_redirect_frontend
+  # HTTP backend to redirect everything to HTTPS
+  bind :::80 v4v6
+  mode http
+  http-request redirect scheme https
+
+frontend https_sni_frontend
+  # TCP backend to forward to HTTPS backends based on SNI
+  bind :::443 v4v6
+  mode tcp
+
+  # Wait up to 5s for a SNI header & only accept TLS connections
+  tcp-request inspect-delay 5s
+  tcp-request content capture req.ssl_sni len 255
+  log-format "%ci:%cp -> %[capture.req.hdr(0)] @ %f (%fi:%fp) -> %b (%bi:%bp)"
+  tcp-request content accept if { req.ssl_hello_type 1 }
+
+  ###################################################
+  ## Rules: forward to HTTPS(S) header-based rules ##
+  ###################################################
+  acl use_http_backend req.ssl_sni -i "codeberg.org"
+  acl use_http_backend req.ssl_sni -i "join.codeberg.org"
+  use_backend https_termination_backend if use_http_backend
+
+  ############################
+  ## Rules: HTTPS SNI-based ##
+  ############################
+  # use_backend xyz_backend if { req.ssl_sni -i "xyz" }
+  default_backend pages_backend
+
+frontend https_termination_frontend
+  # Terminate TLS for HTTP backends
+  bind /tmp/haproxy-tls-termination.sock accept-proxy ssl strict-sni alpn h2,http/1.1 crt /etc/ssl/private/haproxy/
+  mode http
+
+  # HSTS (63072000 seconds)
+  http-response set-header Strict-Transport-Security max-age=63072000
+
+  http-request capture req.hdr(Host) len 255
+  log-format "%ci:%cp -> %[capture.req.hdr(0)] @ %f (%fi:%fp) -> %b (%bi:%bp)"
+
+  ##################################
+  ## Rules: HTTPS(S) header-based ##
+  ##################################
+  use_backend gitea_backend if { hdr(host) -i codeberg.org }
+
+backend https_termination_backend
+  # Redirect to the terminating HTTPS frontend for all HTTP backends
+  server https_termination_server /tmp/haproxy-tls-termination.sock send-proxy-v2-ssl-cn
+  mode tcp
+
+###############################
+## Backends: HTTPS SNI-based ##
+###############################
+
+backend pages_backend
+  # Pages server is a HTTP backend that uses its own certificates for custom domains
+  server pages_server pages:443
+  mode tcp
+
+####################################
+## Backends: HTTP(S) header-based ##
+####################################
+
+backend gitea_backend
+  server gitea_server gitea:80
+  mode http
diff --git a/haproxy-sni/pages-www/index.html b/haproxy-sni/pages-www/index.html
new file mode 100644
index 0000000..dc24785
--- /dev/null
+++ b/haproxy-sni/pages-www/index.html
@@ -0,0 +1 @@
+Hello to Pages!
diff --git a/haproxy-sni/pages.Caddyfile b/haproxy-sni/pages.Caddyfile
new file mode 100644
index 0000000..17adc19
--- /dev/null
+++ b/haproxy-sni/pages.Caddyfile
@@ -0,0 +1,4 @@
+https://example-page.org
+
+tls internal
+file_server
diff --git a/haproxy-sni/test.sh b/haproxy-sni/test.sh
new file mode 100755
index 0000000..89e2dfd
--- /dev/null
+++ b/haproxy-sni/test.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+if [ $# -gt 0 ]; then
+  exec curl -k --resolve '*:443:127.0.0.1' "$@"
+fi
+
+fail() {
+  echo "[FAIL] $@"
+  exit 1
+}
+
+echo "Connecting to Gitea..."
+res=$(curl https://codeberg.org -sk --resolve '*:443:127.0.0.1' --trace-ascii gitea.dump | tee /dev/stderr)
+echo "$res" | grep -Fx 'Hello to Gitea!' >/dev/null || fail "Gitea didn't answer"
+grep '^== Info:  issuer: O=mkcert development CA;' gitea.dump || { grep grep '^== Info:  issuer:' gitea.dump; fail "Gitea didn't use the correct certificate!"; }
+
+echo "Connecting to Pages..."
+res=$(curl https://example-page.org -sk --resolve '*:443:127.0.0.1' --trace-ascii pages.dump | tee /dev/stderr)
+echo "$res" | grep -Fx 'Hello to Pages!' >/dev/null || fail "Pages didn't answer"
+grep '^== Info:  issuer: CN=Caddy Local Authority\b' pages.dump || { grep '^== Info:  issuer:' pages.dump; fail "Pages didn't use the correct certificate!"; }
+
+echo "All tests succeeded"
+rm *.dump