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-ubuntuquickstart container; it temporarily edits and reloads that container's configuration. - Install OpenSSL, jq, and a curl build whose
Featuresline includesHTTP3. 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 && echoConfirm 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}")" = 200Protect 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
BASHIf 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.crtExpected: 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-alpineLoad 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.extThe 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
| Symptom | Cause | Fix |
|---|---|---|
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. |