global daemon ca-base /etc/ssl/certs crt-base /etc/ssl/private master-worker maxconn "${HAPROXY_MAXCONN}" log stdout format raw local0 debug lua-load /etc/haproxy/scripts/register-servers.lua lua-load-per-thread /etc/haproxy/scripts/register-bot-check.lua stats socket /var/run/haproxy.sock mode 666 level admin stats socket 127.0.0.1:1999 level admin httpclient.ssl.verify none # Allow larger buffer size for return-file of argon scripts tune.bufsize 51200 defaults log global mode http option dontlognull option httplog timeout connect 5000ms timeout client 50000ms timeout server 50000ms timeout tarpit 5000ms http-error status 400 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/400.http http-error status 403 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/403.http http-error status 408 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/408.http http-error status 429 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/429.http http-error status 500 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/500.http http-error status 502 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/502.http http-error status 503 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/503.http http-error status 504 content-type "text/html; charset=utf-8" lf-file /etc/haproxy/errors/504.http program api command dataplaneapi -f /etc/haproxy/dataplaneapi.yml --update-map-files no option start-on-reload frontend stats-frontend bind 127.0.0.1:2000 option tcplog mode tcp acl white_list src 127.0.0.1 tcp-request connection reject unless white_list default_backend stats-backend backend stats-backend mode tcp server stats-localhost 127.0.0.1:1999 frontend http-in # Clearnet http (you'll have to figure out https yourself) bind *:80 # bind *:443 ssl crt /etc/haproxy/certs/haproxy.pem alpn h3,h2,http/1.1 # bind quic4@*:443 ssl crt /etc/haproxy/certs/haproxy.pem # http-response set-header alt-svc "h3=\":443\";ma=900;" # Or instead, for Tor, to use circuit IDs as "IP": #bind 127.0.0.1:80 accept-proxy #option forwardfor # optional geoip handling (maps required) and alt-svc header addition http-request set-var(req.xcc) src,map_ip(/etc/haproxy/map/geoip.map) http-request set-var(req.asn) src,map_ip(/etc/haproxy/map/iptoasn.map) http-request set-var(txn.xcn) var(req.xcc),map(/etc/haproxy/map/cctocn.map) http-request set-header X-Country-Code %[var(req.xcc)] http-request set-header X-Continent-Code %[var(txn.xcn)] http-request set-header X-ASN %[var(req.asn)] # drop requests with invalid host header acl is_existing_vhost hdr(host),lower,map_str(/etc/haproxy/map/hosts.map) -m found http-request silent-drop unless is_existing_vhost # debug information at /.basedflare/cgi/trace http-request return status 200 content-type "text/plain; charset=utf-8" lf-file /etc/haproxy/template/trace.txt if { path /.basedflare/cgi/trace } http-request track-sc0 query table count_qs_throttle if { query -m found } http-request redirect location http://%[hdr(host)]/%[table_cnt(count_qs_throttle)] code 302 if TRUE # acl for blocked IPs/subnets/ASN/country http-request lua.set-lang-json acl found_in_blockedip_map src,map_ip(/etc/haproxy/map/blockedip.map) -m found acl found_in_blockedasn_map var(req.asn),map(/etc/haproxy/map/blockedasn.map) -m found acl found_in_blockedcc_map var(req.xcc),map(/etc/haproxy/map/blockedcc.map) -m found acl found_in_blockedcn_map var(txn.xcn),map(/etc/haproxy/map/blockedcn.map) -m found acl blocked_bool var(txn.blocked_bool) -m bool http-request lua.set-ip-var blockedip txn.blocked_bool ip if found_in_blockedip_map http-request lua.set-ip-var blockedasn txn.blocked_bool asn if found_in_blockedasn_map http-request lua.set-ip-var blockedcc txn.blocked_bool cc if found_in_blockedcc_map http-request lua.set-ip-var blockedcn txn.blocked_bool cn if found_in_blockedcn_map http-request deny deny_status 403 if blocked_bool # ratelimit (and for tor, kill circuit) on POST bot-check. legitimate users shouldn't hit this. # http-request track-sc0 src table bot_check_post_throttle if { path /.basedflare/bot-check } { method POST } # http-request lua.kill-tor-circuit if { sc_http_req_rate(0) gt 1 } # http-request tarpit if { sc_http_req_rate(0) gt 1 } # acl for lua check whitelisted IPs/subnets and some excluded paths acl found_in_whitelist_map src,map_ip(/etc/haproxy/map/whitelist.map) -m found acl is_excluded var(txn.whitelist_ip_or_subnet) -m bool http-request lua.set-ip-var whitelist txn.whitelist_ip_or_subnet ip if found_in_whitelist_map acl is_excluded src -f /etc/haproxy/map/crawler-whitelist.map acl is_excluded path /favicon.ico /.basedflare/pow-icon #add more # acl ORs for when ddos_mode_enabled acl ddos_mode_enabled_override str("true"),map(/etc/haproxy/map/ddos_global.map) -m found acl ddos_mode_enabled hdr(host),lower,map(/etc/haproxy/map/ddos.map) -m found acl ddos_mode_enabled base,map(/etc/haproxy/map/ddos.map) -m found # serve challenge page scripts directly from haproxy http-request return file /etc/haproxy/js/auto.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/auto.min.js } http-request return file /etc/haproxy/js/argon2.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/argon2.min.js } http-request return file /etc/haproxy/js/challenge.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/challenge.min.js } http-request return file /etc/haproxy/js/worker.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "Cache-Control" "public, max-age=86400" if { path /.basedflare/js/worker.min.js } # acl for domains in maintenance mode to return maintenance page (after challenge page htp-request return rules, for the footerlogo) acl maintenance_mode hdr(host),lower,map_str(/etc/haproxy/map/maintenance.map) -m found #http-request lua.set-lang-json http-request return lf-file /etc/haproxy/template/maintenance.html status 200 content-type "text/html; charset=utf-8" hdr "Cache-Control" "private, max-age=30" if maintenance_mode # rewrite specific domain+path to domain or domain+path http-request redirect location https://%[base,map(/etc/haproxy/map/rewrite.map)] code 302 if { base,map(/etc/haproxy/map/rewrite.map) -i -m found } # redirect domain to domain or domain+path http-request redirect location https://%[hdr(host),map(/etc/haproxy/map/redirect.map)] code 302 if { hdr(host),map(/etc/haproxy/map/redirect.map) -i -m found } # create acl for bools updated by lua acl captcha_passed var(txn.captcha_passed) -m bool acl pow_passed var(txn.pow_passed) -m bool acl validate_captcha var(txn.validate_captcha) -m bool acl validate_pow var(txn.validate_pow) -m bool # check pow/captcha and show page if necessary acl on_bot_check path /.basedflare/bot-check http-request use-service lua.bot-check if on_bot_check !is_excluded # challenge decisions, checking, and redirecting to /bot-check http-request lua.decide-checks-necessary if !is_excluded !on_bot_check ddos_mode_enabled http-request lua.captcha-check if !is_excluded !on_bot_check validate_captcha http-request lua.pow-check if !is_excluded !on_bot_check validate_pow OR !is_excluded !on_bot_check ddos_mode_enabled_override http-request redirect location /.basedflare/bot-check?%[capture.req.uri] code 302 if validate_captcha !captcha_passed !on_bot_check ddos_mode_enabled !is_excluded OR validate_pow !pow_passed !on_bot_check ddos_mode_enabled !is_excluded OR !pow_passed ddos_mode_enabled_override !on_bot_check !is_excluded # X-Cache-Status header (may be sent in some non-cache responses because NOSRV can happen for other reasons, but should always be present in responses served by cache-use) http-response set-header X-Cache-Status HIT if !{ srv_id -m found } http-response set-header X-Cache-Status MISS if { srv_id -m found } # simple example cache for files http-request set-var(txn.path) path acl can_cache var(txn.path) -i -m end .png .jpg .jpeg .jpe .ico .webmanifest .xml .apng .bmp .webp .pjpeg .jfif .gif .mp4 .webm .mov .mkv .svg .m4a .aac .flac .mp3 .ogg .wav .opus .txt .pdf .sid # optional alt-svc header (done after cache so not set in cached responses acl match_server_continent var(txn.xcn) -m str "${HAPROXY_CONTINENT}" http-response set-header X-Server-CN "${HAPROXY_CONTINENT}" http-response set-header X-User-CN %[var(txn.xcn)] http-response set-header Alt-Svc %[var(txn.xcn),map(/etc/haproxy/map/alt-svc.map)] if !match_server_continent # header checks for no caching # acl auth_cookie_set res.hdr(Set-Cookie),lower -m found # acl cache_control_max_age_0 res.fhdr(Cache-Control,0) -m sub "max-age=0" # acl cache_control_max_age_0 res.fhdr(Cache-Control,1) -m sub "max-age=0" # acl cache_control_max_age_0 res.fhdr(Cache-Control,2) -m sub "max-age=0" # basic caching # http-response set-header Cache-Control no-cache if auth_cookie_set # http-response cache-store basic_cache if !auth_cookie_set !cache_control_max_age_0 # http-request cache-use basic_cache default_backend servers cache basic_cache total-max-size "${HAPROXY_CACHE_MB}" max-object-size 31457280 max-age 86400 process-vary on backend servers balance leastconn use-server %[lua.get_server_names] if TRUE backend bot_check_post_throttle stick-table type ipv6 size 100k expire 60s store http_req_rate(60s) backend count_qs_throttle stick-table type string size 100k expire 60s store http_req_rate(60s) backend hcaptcha mode http server hcaptcha hcaptcha.com:443 backend recaptcha mode http server recaptcha www.google.com:443