Docs
Apache APISIXManage traffic and APIsTraffic ManagementConfigure HTTP/3 QUIC Between Client and APISIX

Configure HTTP/3 QUIC Between Client and APISIX

Enable experimental downstream HTTP/3 on a disposable APISIX quickstart and verify QUIC with a trusted test certificate.

Applies to:Apache APISIX 3.18.0

Deployments:DockerRoles:Platform engineerTime:15 minutesImpact:Restarts the data plane

Goal

Enable HTTP/3 on the APISIX quickstart's TLS port, create a certificate and one isolated route, and prove that a client negotiated HTTP/3 rather than silently falling back to HTTP/2 or HTTP/1.1.

APISIX supports HTTP/3 only between downstream clients and the gateway. Its upstream connection remains HTTP or HTTPS. HTTP/3 support is experimental in APISIX 3.18.0; evaluate it in a disposable environment before considering production use.

Before you begin

  • Complete Get APISIX on a machine with Docker. This procedure is deliberately limited to a dedicated apache/apisix:3.18.0-ubuntu quickstart container; it temporarily edits and reloads that container's configuration.
  • Install OpenSSL, jq, and a curl build whose Features line includes HTTP3. Many operating-system curl packages do not include HTTP/3.
  • Stop if this is a shared or production gateway. Follow your normal configuration-management and certificate procedures there instead.

Set the variables used in this guide:

export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export HTTP3_HOST="http3.test"
read -rsp "Admin API key (press Enter for an unchanged quickstart): " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echo

Confirm the expected image, TCP and UDP port publications, curl capability, and Admin API access:

docker inspect apisix-quickstart | jq -e '.[0].Config.Image == "apache/apisix:3.18.0-ubuntu"'
docker port apisix-quickstart 9443/tcp | grep -q ':9443$'
docker port apisix-quickstart 9443/udp | grep -q ':9443$'
curl --version | grep '^Features:' | grep -qw HTTP3
test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/routes" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 200

Protect existing resources and artifacts:

for resource in routes/http3-guide-route ssls/http3-guide-ssl; do
  test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/${resource}" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 404 || { echo "Stop: ${resource} already exists"; exit 1; }
done
test -z "$(docker ps -aq --filter name=^/http3-guide-upstream$)" || { echo "Stop: container http3-guide-upstream already exists"; exit 1; }
for file in http3-ca.key http3-ca.crt http3-ca.srl http3-server.key http3-server.csr http3-server.crt http3-server.ext; do
  test ! -e "${file}" || { echo "Stop: ${file} already exists"; exit 1; }
done
docker exec apisix-quickstart bash -ceu '
  test ! -e /usr/local/apisix/conf/config.yaml.http3-guide-backup
  ! grep -q "^  ssl:" /usr/local/apisix/conf/config.yaml
'

The last check intentionally stops if the current APISIX configuration already has an apisix.ssl block. Merge the listener into that managed configuration instead of letting this quickstart-only procedure replace it.

Steps

Step 1: Add the HTTP/3 listener

Back up the current quickstart configuration inside the container, insert one HTTP/3-enabled TLS listener, test the generated NGINX configuration, and reload APISIX:

docker exec -i apisix-quickstart bash -se <<'BASH'
conf=/usr/local/apisix/conf/config.yaml
backup=/usr/local/apisix/conf/config.yaml.http3-guide-backup
cp "$conf" "$backup"
sed -i '/^apisix:$/a\
  ssl:\
    listen:\
      - port: 9443\
        enable_http3: true
' "$conf"
apisix test
apisix reload
BASH

If apisix test fails, restore the backup before continuing:

docker exec apisix-quickstart bash -ceu '
  mv /usr/local/apisix/conf/config.yaml.http3-guide-backup /usr/local/apisix/conf/config.yaml
  apisix test
  apisix reload
'

Step 2: Generate a test CA and server certificate

Create a local CA and a server certificate whose subject alternative name matches HTTP3_HOST:

openssl genrsa -out http3-ca.key 2048
openssl req -x509 -new -sha256 -days 30 -key http3-ca.key -out http3-ca.crt \
  -subj "/CN=HTTP3 Guide CA" \
  -addext "basicConstraints=critical,CA:TRUE" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out http3-server.key 2048
openssl req -new -sha256 -key http3-server.key -out http3-server.csr -subj "/CN=${HTTP3_HOST}"
printf 'subjectAltName=DNS:%s\nextendedKeyUsage=serverAuth\nkeyUsage=digitalSignature,keyEncipherment\n' "${HTTP3_HOST}" > http3-server.ext
openssl x509 -req -sha256 -days 30 -in http3-server.csr -CA http3-ca.crt -CAkey http3-ca.key -CAcreateserial -extfile http3-server.ext -out http3-server.crt
openssl verify -CAfile http3-ca.crt -verify_hostname "${HTTP3_HOST}" http3-server.crt

Expected: http3-server.crt: OK.

Step 3: Create the certificate and route

Start one disposable upstream on the quickstart network:

docker run -d --name http3-guide-upstream --network apisix-quickstart-net nginx:1.29-alpine

Load the certificate and private key without embedding either one in shell quoting:

jq -n --arg sni "${HTTP3_HOST}" --rawfile cert http3-server.crt --rawfile key http3-server.key '{
  sni: $sni,
  cert: $cert,
  key: $key
}' | curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/ssls/http3-guide-ssl" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" --data-binary @-

Create a route whose upstream connection is ordinary HTTP. This keeps the downstream HTTP/3 boundary explicit:

curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/routes/http3-guide-route" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "uri": "/http3",
    "plugins": {"proxy-rewrite": {"uri": "/"}},
    "upstream": {
      "type": "roundrobin",
      "nodes": {"http3-guide-upstream:80": 1}
    }
  }'

Validate

Require curl to use HTTP/3, trust only the test CA, resolve the test SNI locally, and print the negotiated protocol and status:

HTTP3_RESULT=$(curl -sS --http3-only --connect-timeout 5 --max-time 20 \
  --cacert http3-ca.crt --resolve "${HTTP3_HOST}:9443:127.0.0.1" \
  -o /dev/null -w '%{http_version} %{http_code}' "https://${HTTP3_HOST}:9443/http3")
test "${HTTP3_RESULT}" = "3 200"
printf 'HTTP version and status: %s\n' "${HTTP3_RESULT}"

Expected: HTTP version and status: 3 200. The --http3-only option makes connection failure visible instead of negotiating an older protocol.

Roll back / Clean up

Delete the route before its certificate, restore the exact configuration backup, reload APISIX, then remove the disposable container and local key material:

curl -sS --connect-timeout 3 --max-time 15 -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/http3-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -sS --connect-timeout 3 --max-time 15 -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/ssls/http3-guide-ssl" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
docker exec apisix-quickstart bash -ceu '
  mv /usr/local/apisix/conf/config.yaml.http3-guide-backup /usr/local/apisix/conf/config.yaml
  apisix test
  apisix reload
'
docker rm -f http3-guide-upstream
rm -f http3-ca.key http3-ca.crt http3-ca.srl http3-server.key http3-server.csr http3-server.crt http3-server.ext

The configuration restore removes the experimental listener. The quickstart still publishes port 9443 over TCP and UDP, but APISIX no longer listens for QUIC there.

Troubleshoot

SymptomCauseFix
curl reports that --http3-only is unsupported.That curl build lacks HTTP/3 support.Install an HTTP/3-enabled build and confirm HTTP3 appears in curl --version.
The UDP port check fails.The APISIX container was started without a 9443/udp publication.Recreate the disposable container with both -p 9443:9443/tcp and -p 9443:9443/udp; Docker cannot add a port to an existing container.
apisix test fails after editing the listener.The current configuration already differs from the quickstart shape or the insertion produced invalid YAML.Restore the backup immediately and merge apisix.ssl.listen through the deployment's normal configuration workflow.
curl reports a certificate-name error.The request host, SNI, certificate SAN, or --resolve host differs.Use the same HTTP3_HOST value in certificate generation, the SSL object, and validation.
curl times out although TCP HTTPS works.UDP 9443 is blocked or QUIC is not listening.Check host/container UDP publication and any local firewall; HTTP/3 uses UDP, not TCP.
The request returns 502.APISIX cannot resolve or reach http3-guide-upstream.Confirm the upstream container is running on apisix-quickstart-net.