Hi, I've discovered it is possible to make haproxy (2.0.9 and 2.0.10, at least) produce duplicate headers using the set-header directive (which is defined as "This does the same as "http-request add-header" except that the header name is first removed if it existed.").
This only happens when HTX is enabled. The essential part is that the variable substitution uses values that are not set. Here is my configuration, narrowed down to its essentials; defaults mode http option http-use-htx timeout client 30s timeout connect 5s timeout queue 5s timeout server 30s timeout tarpit 10s frontend http-in bind 127.0.0.1:5984 acl header_is_set hdr_len(X-Header) gt 0 http-request set-header X-Header %[hdr(x-foo)] unless header_is_set http-request set-header X-Header %[hdr(x-bar)] unless header_is_set http-request set-header X-Header baz unless header_is_set default_backend http-out backend http-out server db1 127.0.0.1:15984 check inter 1s Here is the non-HTX output; GET / HTTP/1.1 Host: localhost:5984 User-Agent: curl/7.64.1 Accept: */* X-Header: baz Here is the HTX output; GET / HTTP/1.1 host: localhost:5984 user-agent: curl/7.64.1 accept: */* x-header: x-header: x-header: baz and my version info in detail; HA-Proxy version 2.0.10 2019/11/25 - https://haproxy.org/ Build options : TARGET = linux-glibc CPU = generic CC = gcc CFLAGS = -m64 -march=x86-64 -O2 -g -fno-strict-aliasing -Wdeclaration-after-statement -fwrapv -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-old-style-declaration -Wno-ignored-qualifiers -Wno-clobbered -Wno-missing-field-initializers -Wtype-limits OPTIONS = USE_PCRE2=1 USE_PCRE2_JIT=1 USE_THREAD=1 USE_PTHREAD_PSHARED=1 USE_REGPARM=1 USE_STATIC_PCRE2=1 USE_OPENSSL=1 USE_LUA=1 USE_SLZ=1 USE_TFO=1 USE_SYSTEMD=1 Feature list : +EPOLL -KQUEUE -MY_EPOLL -MY_SPLICE +NETFILTER -PCRE -PCRE_JIT +PCRE2 +PCRE2_JIT +POLL -PRIVATE_CACHE +THREAD +PTHREAD_PSHARED +REGPARM -STATIC_PCRE +STATIC_PCRE2 +TPROXY +LINUX_TPROXY +LINUX_SPLICE +LIBCRYPT +CRYPT_H -VSYSCALL +GETADDRINFO +OPENSSL +LUA +FUTEX +ACCEPT4 -MY_ACCEPT4 -ZLIB +SLZ +CPU_AFFINITY +TFO +NS +DL +RT -DEVICEATLAS -51DEGREES -WURFL +SYSTEMD -OBSOLETE_LINKER +PRCTL +THREAD_DUMP -EVPORTS Default settings : bufsize = 16384, maxrewrite = 1024, maxpollevents = 200 Built with multi-threading support (MAX_THREADS=64, default=8). Built with OpenSSL version : OpenSSL 1.1.1d 10 Sep 2019 Running on OpenSSL version : OpenSSL 1.1.1d 10 Sep 2019 OpenSSL library supports TLS extensions : yes OpenSSL library supports SNI : yes OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3 Built with Lua version : Lua 5.3.4 Built with network namespace support. Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND Built with libslz for stateless compression. Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip") Built with PCRE2 version : 10.30 2017-08-14 PCRE2 library supports JIT : yes Encrypted password support via crypt(3): yes Available polling systems : epoll : pref=300, test result OK poll : pref=200, test result OK select : pref=150, test result OK Total: 3 (3 usable), will use epoll. Available multiplexer protocols : (protocols marked as <default> cannot be specified using 'proto' keyword) h2 : mode=HTX side=FE|BE mux=H2 h2 : mode=HTTP side=FE mux=H2 <default> : mode=HTX side=FE|BE mux=H1 <default> : mode=TCP|HTTP side=FE|BE mux=PASS Available services : none Available filters : [SPOE] spoe [COMP] compression [CACHE] cache [TRACE] trace