mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-03 03:01:31 +08:00
The http_protocols setting expects a comma-separated string, not a list. Updated all examples in the HTTP/2 guide. Fixes #3561
849 lines
23 KiB
Markdown
849 lines
23 KiB
Markdown
# HTTP/2 Support
|
||
|
||
!!! warning "Beta Feature"
|
||
HTTP/2 support is a beta feature introduced in Gunicorn 25.0.0. While it has been tested,
|
||
the API and behavior may change in future releases. Please report any issues on
|
||
[GitHub](https://github.com/benoitc/gunicorn/issues).
|
||
|
||
Gunicorn supports HTTP/2 (RFC 7540) for improved performance with modern clients.
|
||
HTTP/2 provides multiplexed streams, header compression, and other optimizations
|
||
over HTTP/1.1.
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
# Install gunicorn with HTTP/2 support
|
||
pip install gunicorn[http2]
|
||
|
||
# Run with HTTP/2 enabled (requires SSL)
|
||
gunicorn myapp:app \
|
||
--worker-class gthread \
|
||
--threads 4 \
|
||
--certfile server.crt \
|
||
--keyfile server.key \
|
||
--http-protocols h2,h1
|
||
```
|
||
|
||
## Requirements
|
||
|
||
HTTP/2 support requires:
|
||
|
||
- **SSL/TLS**: HTTP/2 uses ALPN (Application-Layer Protocol Negotiation) which
|
||
requires an encrypted connection
|
||
- **h2 library**: Install with `pip install gunicorn[http2]` or `pip install h2`
|
||
- **Compatible worker**: gthread, gevent, or ASGI workers
|
||
|
||
## Configuration
|
||
|
||
### Enable HTTP/2
|
||
|
||
Enable HTTP/2 by setting the `--http-protocols` option:
|
||
|
||
```bash
|
||
gunicorn myapp:app --http-protocols h2,h1
|
||
```
|
||
|
||
Or in a configuration file:
|
||
|
||
```python
|
||
# gunicorn.conf.py
|
||
http_protocols = "h2, h1"
|
||
```
|
||
|
||
The order matters for ALPN negotiation - protocols are tried in order of preference.
|
||
|
||
| Protocol | Description |
|
||
|----------|-------------|
|
||
| `h2` | HTTP/2 over TLS |
|
||
| `h1` | HTTP/1.1 (fallback) |
|
||
|
||
!!! note
|
||
Always include `h1` as a fallback for clients that don't support HTTP/2.
|
||
|
||
### SSL/TLS Configuration
|
||
|
||
HTTP/2 requires SSL/TLS. Configure certificates:
|
||
|
||
```bash
|
||
gunicorn myapp:app \
|
||
--certfile /path/to/server.crt \
|
||
--keyfile /path/to/server.key \
|
||
--http-protocols h2,h1
|
||
```
|
||
|
||
Or in a configuration file:
|
||
|
||
```python
|
||
# gunicorn.conf.py
|
||
certfile = "/path/to/server.crt"
|
||
keyfile = "/path/to/server.key"
|
||
http_protocols = "h2, h1"
|
||
```
|
||
|
||
### HTTP/2 Settings
|
||
|
||
Fine-tune HTTP/2 behavior with these settings:
|
||
|
||
| Setting | Default | Description |
|
||
|---------|---------|-------------|
|
||
| `http2_max_concurrent_streams` | 100 | Maximum concurrent streams per connection |
|
||
| `http2_initial_window_size` | 65535 | Initial flow control window size (bytes) |
|
||
| `http2_max_frame_size` | 16384 | Maximum frame size (bytes) |
|
||
| `http2_max_header_list_size` | 65536 | Maximum header list size (bytes) |
|
||
|
||
Example configuration:
|
||
|
||
```python
|
||
# gunicorn.conf.py
|
||
http_protocols = "h2, h1"
|
||
http2_max_concurrent_streams = 200
|
||
http2_initial_window_size = 1048576 # 1MB
|
||
```
|
||
|
||
## Worker Compatibility
|
||
|
||
Not all workers support HTTP/2:
|
||
|
||
| Worker | HTTP/2 Support | Notes |
|
||
|--------|----------------|-------|
|
||
| `sync` | No | Single-threaded, cannot multiplex streams |
|
||
| `gthread` | Yes | Recommended for HTTP/2 |
|
||
| `gevent` | Yes | Requires gevent |
|
||
| `eventlet` | Yes | **Deprecated** - will be removed in 26.0 |
|
||
| `asgi` | Yes | For async frameworks |
|
||
| `tornado` | No | Tornado handles its own protocol |
|
||
|
||
If you use the sync or tornado worker with HTTP/2 enabled, Gunicorn will log a
|
||
warning and fall back to HTTP/1.1.
|
||
|
||
### Recommended: gthread Worker
|
||
|
||
For HTTP/2, the gthread worker is recommended:
|
||
|
||
```bash
|
||
gunicorn myapp:app \
|
||
--worker-class gthread \
|
||
--threads 4 \
|
||
--workers 2 \
|
||
--http-protocols h2,h1 \
|
||
--certfile server.crt \
|
||
--keyfile server.key
|
||
```
|
||
|
||
## HTTP 103 Early Hints
|
||
|
||
Gunicorn supports HTTP 103 Early Hints (RFC 8297), allowing servers to send
|
||
resource hints before the final response. This enables browsers to preload
|
||
CSS, JavaScript, and other assets in parallel.
|
||
|
||
### WSGI Applications
|
||
|
||
Use the `wsgi.early_hints` callback in your WSGI application:
|
||
|
||
```python
|
||
def app(environ, start_response):
|
||
# Send early hints if available
|
||
if 'wsgi.early_hints' in environ:
|
||
environ['wsgi.early_hints']([
|
||
('Link', '</style.css>; rel=preload; as=style'),
|
||
('Link', '</app.js>; rel=preload; as=script'),
|
||
])
|
||
|
||
# Continue with the actual response
|
||
start_response('200 OK', [('Content-Type', 'text/html')])
|
||
return [b'<html>...</html>']
|
||
```
|
||
|
||
### ASGI Applications
|
||
|
||
Use the `http.response.informational` message type:
|
||
|
||
```python
|
||
async def app(scope, receive, send):
|
||
# Send early hints
|
||
await send({
|
||
"type": "http.response.informational",
|
||
"status": 103,
|
||
"headers": [
|
||
(b"link", b"</style.css>; rel=preload; as=style"),
|
||
(b"link", b"</app.js>; rel=preload; as=script"),
|
||
],
|
||
})
|
||
|
||
# Send the actual response
|
||
await send({
|
||
"type": "http.response.start",
|
||
"status": 200,
|
||
"headers": [(b"content-type", b"text/html")],
|
||
})
|
||
await send({
|
||
"type": "http.response.body",
|
||
"body": b"<html>...</html>",
|
||
})
|
||
```
|
||
|
||
!!! note
|
||
Early hints are only sent to HTTP/1.1+ clients. HTTP/1.0 clients silently
|
||
ignore the callback since they don't support 1xx responses.
|
||
|
||
## Stream Priority
|
||
|
||
HTTP/2 allows clients to indicate the relative priority of streams using PRIORITY frames
|
||
(RFC 7540 Section 5.3). Gunicorn tracks stream priorities and exposes them to both
|
||
WSGI and ASGI applications.
|
||
|
||
### Accessing Priority in WSGI
|
||
|
||
Priority information is available in the WSGI environ for HTTP/2 requests:
|
||
|
||
```python
|
||
def app(environ, start_response):
|
||
# Access stream priority (HTTP/2 only)
|
||
weight = environ.get('gunicorn.http2.priority_weight')
|
||
depends_on = environ.get('gunicorn.http2.priority_depends_on')
|
||
|
||
if weight is not None:
|
||
# This is an HTTP/2 request with priority info
|
||
# Higher weight = client considers this more important
|
||
print(f"Request priority: weight={weight}, depends_on={depends_on}")
|
||
|
||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||
return [b'OK']
|
||
```
|
||
|
||
| Environ Key | Range | Default | Description |
|
||
|-------------|-------|---------|-------------|
|
||
| `gunicorn.http2.priority_weight` | 1-256 | 16 | Higher weight = more resources |
|
||
| `gunicorn.http2.priority_depends_on` | Stream ID | 0 | Parent stream (0 = root) |
|
||
|
||
### Accessing Priority in ASGI
|
||
|
||
For ASGI applications, priority is available in the scope's `extensions` dict:
|
||
|
||
```python
|
||
async def app(scope, receive, send):
|
||
if scope["type"] == "http":
|
||
# Check for HTTP/2 priority extension
|
||
extensions = scope.get("extensions", {})
|
||
priority = extensions.get("http.response.priority")
|
||
|
||
if priority:
|
||
weight = priority["weight"] # 1-256
|
||
depends_on = priority["depends_on"] # Parent stream ID
|
||
print(f"Request priority: weight={weight}, depends_on={depends_on}")
|
||
|
||
await send({
|
||
"type": "http.response.start",
|
||
"status": 200,
|
||
"headers": [(b"content-type", b"text/plain")],
|
||
})
|
||
await send({
|
||
"type": "http.response.body",
|
||
"body": b"OK",
|
||
})
|
||
```
|
||
|
||
| Extension Key | Field | Range | Default | Description |
|
||
|---------------|-------|-------|---------|-------------|
|
||
| `http.response.priority` | `weight` | 1-256 | 16 | Higher weight = more resources |
|
||
| `http.response.priority` | `depends_on` | Stream ID | 0 | Parent stream (0 = root) |
|
||
|
||
!!! note
|
||
Stream priority is advisory. Applications can use it for scheduling decisions,
|
||
but Gunicorn does not enforce priority-based request ordering. Priority
|
||
information is only present for HTTP/2 requests.
|
||
|
||
## Response Trailers
|
||
|
||
HTTP/2 supports trailing headers (trailers) sent after the response body.
|
||
This is commonly used for gRPC status codes, checksums, and timing information.
|
||
|
||
### WSGI Applications
|
||
|
||
For WSGI applications, use the `gunicorn.http2.send_trailers` callback in the environ:
|
||
|
||
```python
|
||
def app(environ, start_response):
|
||
# Get trailer callback (HTTP/2 only)
|
||
send_trailers = environ.get('gunicorn.http2.send_trailers')
|
||
|
||
# Announce trailers in response headers
|
||
headers = [
|
||
('Content-Type', 'application/grpc'),
|
||
('Trailer', 'grpc-status, grpc-message'),
|
||
]
|
||
start_response('200 OK', headers)
|
||
|
||
# Yield response body
|
||
yield b'response data'
|
||
|
||
# Send trailers after body (if available)
|
||
if send_trailers:
|
||
send_trailers([
|
||
('grpc-status', '0'),
|
||
('grpc-message', 'OK'),
|
||
])
|
||
```
|
||
|
||
### ASGI Applications
|
||
|
||
For ASGI applications, use the `http.response.trailers` extension:
|
||
|
||
```python
|
||
async def app(scope, receive, send):
|
||
# Send response with trailers flag
|
||
await send({
|
||
"type": "http.response.start",
|
||
"status": 200,
|
||
"headers": [
|
||
(b"content-type", b"application/grpc"),
|
||
(b"trailer", b"grpc-status, grpc-message"),
|
||
],
|
||
})
|
||
|
||
# Send body
|
||
await send({
|
||
"type": "http.response.body",
|
||
"body": b"response data",
|
||
"more_body": False,
|
||
})
|
||
|
||
# Send trailers (HTTP/2 only)
|
||
if "http.response.trailers" in scope.get("extensions", {}):
|
||
await send({
|
||
"type": "http.response.trailers",
|
||
"headers": [
|
||
(b"grpc-status", b"0"),
|
||
(b"grpc-message", b"OK"),
|
||
],
|
||
})
|
||
```
|
||
|
||
### Trailer Rules (RFC 7540)
|
||
|
||
- Trailers MUST NOT include pseudo-headers (`:status`, `:path`, etc.)
|
||
- Announce trailers using the `Trailer` response header
|
||
- Trailers are only available in HTTP/2 (HTTP/1.1 chunked encoding not supported)
|
||
|
||
### Common Use Cases
|
||
|
||
| Use Case | Trailer Headers |
|
||
|----------|-----------------|
|
||
| gRPC | `grpc-status`, `grpc-message` |
|
||
| Checksums | `Content-MD5`, `Digest` |
|
||
| Timing | `Server-Timing` |
|
||
| Signatures | `Signature` |
|
||
|
||
## Production Deployment
|
||
|
||
### With Nginx
|
||
|
||
Configure nginx to proxy HTTP/2 connections to Gunicorn:
|
||
|
||
```nginx
|
||
upstream gunicorn {
|
||
server 127.0.0.1:8443;
|
||
keepalive 32;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
http2 on;
|
||
server_name example.com;
|
||
|
||
ssl_certificate /path/to/server.crt;
|
||
ssl_certificate_key /path/to/server.key;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
|
||
# Forward 103 Early Hints (requires nginx 1.29+)
|
||
location / {
|
||
proxy_pass https://gunicorn;
|
||
proxy_http_version 1.1;
|
||
proxy_ssl_verify off;
|
||
|
||
early_hints $http2;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
}
|
||
```
|
||
|
||
!!! note
|
||
For nginx to forward 103 Early Hints from upstream, you need nginx 1.29+
|
||
and the [`early_hints`](https://nginx.org/en/docs/http/ngx_http_core_module.html#early_hints) directive.
|
||
|
||
### Direct TLS Termination
|
||
|
||
For simpler deployments, Gunicorn can terminate TLS directly:
|
||
|
||
```python
|
||
# gunicorn.conf.py
|
||
bind = "0.0.0.0:443"
|
||
worker_class = "gthread"
|
||
threads = 4
|
||
workers = 4
|
||
|
||
# SSL
|
||
certfile = "/etc/letsencrypt/live/example.com/fullchain.pem"
|
||
keyfile = "/etc/letsencrypt/live/example.com/privkey.pem"
|
||
|
||
# HTTP/2
|
||
http_protocols = "h2, h1"
|
||
http2_max_concurrent_streams = 100
|
||
```
|
||
|
||
### Recommended Settings
|
||
|
||
For production HTTP/2 deployments:
|
||
|
||
```python
|
||
# gunicorn.conf.py
|
||
worker_class = "gthread"
|
||
workers = 4
|
||
threads = 4
|
||
keepalive = 120 # HTTP/2 connections are long-lived
|
||
|
||
# SSL/TLS
|
||
certfile = "/path/to/server.crt"
|
||
keyfile = "/path/to/server.key"
|
||
ssl_version = "TLSv1_2" # Minimum TLS 1.2 for HTTP/2
|
||
|
||
# HTTP/2
|
||
http_protocols = "h2, h1"
|
||
http2_max_concurrent_streams = 100
|
||
http2_initial_window_size = 65535
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### HTTP/2 not negotiated
|
||
|
||
If clients fall back to HTTP/1.1:
|
||
|
||
1. Verify SSL is configured correctly
|
||
2. Check that `h2` is in `--http-protocols`
|
||
3. Ensure the h2 library is installed: `pip install h2`
|
||
4. Verify ALPN support: `openssl s_client -alpn h2 -connect host:port`
|
||
|
||
### Worker doesn't support HTTP/2
|
||
|
||
If you see "HTTP/2 is not supported by the sync worker":
|
||
|
||
```bash
|
||
# Switch to gthread worker
|
||
gunicorn myapp:app --worker-class gthread --threads 4
|
||
```
|
||
|
||
### Connection errors with large requests
|
||
|
||
Increase flow control window sizes:
|
||
|
||
```python
|
||
http2_initial_window_size = 1048576 # 1MB
|
||
http2_max_frame_size = 32768 # 32KB
|
||
```
|
||
|
||
### Too many concurrent streams
|
||
|
||
If clients report stream limit errors:
|
||
|
||
```python
|
||
http2_max_concurrent_streams = 200 # Increase from default 100
|
||
```
|
||
|
||
## Performance Tuning
|
||
|
||
HTTP/2 performance depends on proper tuning of both Gunicorn and system settings.
|
||
This section covers different tuning profiles and their trade-offs.
|
||
|
||
### Tuning Profiles
|
||
|
||
#### Conservative (Default)
|
||
|
||
Best for: Low to moderate traffic, memory-constrained environments.
|
||
|
||
```python
|
||
# gunicorn.conf.py - Conservative profile
|
||
workers = 2
|
||
worker_class = "gthread"
|
||
threads = 4
|
||
|
||
http2_max_concurrent_streams = 100
|
||
http2_initial_window_size = 65535 # 64KB
|
||
http2_max_frame_size = 16384 # 16KB
|
||
```
|
||
|
||
| Pros | Cons |
|
||
|------|------|
|
||
| Low memory footprint | Limited throughput at high concurrency |
|
||
| Safe defaults per RFC | More round-trips for large transfers |
|
||
| Works on constrained systems | May bottleneck at ~10K req/s |
|
||
|
||
#### Balanced
|
||
|
||
Best for: Moderate traffic, general production use.
|
||
|
||
```python
|
||
# gunicorn.conf.py - Balanced profile
|
||
workers = 4
|
||
worker_class = "gthread"
|
||
threads = 4
|
||
backlog = 2048
|
||
|
||
http2_max_concurrent_streams = 128
|
||
http2_initial_window_size = 262144 # 256KB
|
||
http2_max_frame_size = 16384 # 16KB
|
||
```
|
||
|
||
| Pros | Cons |
|
||
|------|------|
|
||
| Good throughput (15K+ req/s) | More memory per connection |
|
||
| Handles traffic spikes | Requires more CPU |
|
||
| Good balance of resources | |
|
||
|
||
#### High Concurrency
|
||
|
||
Best for: High traffic APIs, microservices, load testing.
|
||
|
||
```python
|
||
# gunicorn.conf.py - High concurrency profile
|
||
workers = 4
|
||
worker_class = "gthread"
|
||
threads = 8
|
||
backlog = 2048
|
||
worker_connections = 10000
|
||
|
||
http2_max_concurrent_streams = 256
|
||
http2_initial_window_size = 1048576 # 1MB
|
||
http2_max_frame_size = 32768 # 32KB
|
||
```
|
||
|
||
| Pros | Cons |
|
||
|------|------|
|
||
| High throughput (20K+ req/s) | Higher memory usage (~4x conservative) |
|
||
| Handles 1000s of clients | Requires system tuning |
|
||
| Better large transfer performance | May overwhelm downstream services |
|
||
|
||
### Setting Trade-offs
|
||
|
||
#### `http2_max_concurrent_streams`
|
||
|
||
Controls how many simultaneous streams a client can open per connection.
|
||
|
||
| Value | Memory | Throughput | Use Case |
|
||
|-------|--------|------------|----------|
|
||
| 50-100 | Low | Moderate | APIs with small payloads |
|
||
| 128-256 | Medium | High | General web applications |
|
||
| 500+ | High | Very High | Streaming, real-time apps |
|
||
|
||
!!! warning
|
||
Very high values (500+) can lead to resource exhaustion under attack.
|
||
Use with rate limiting.
|
||
|
||
#### `http2_initial_window_size`
|
||
|
||
Flow control window size determines how much data can be sent before waiting for acknowledgment.
|
||
|
||
| Value | Memory | Latency | Use Case |
|
||
|-------|--------|---------|----------|
|
||
| 65535 (64KB) | Low | Higher for large transfers | Default, memory-constrained |
|
||
| 262144 (256KB) | Medium | Balanced | General use |
|
||
| 1048576 (1MB) | High | Lower for large transfers | Large file transfers, streaming |
|
||
|
||
!!! note
|
||
Larger windows improve throughput for large responses but increase memory
|
||
usage per stream. Calculate: `max_streams × window_size × connections`.
|
||
|
||
#### `http2_max_frame_size`
|
||
|
||
Maximum size of individual HTTP/2 frames.
|
||
|
||
| Value | Memory | Efficiency | Use Case |
|
||
|-------|--------|------------|----------|
|
||
| 16384 (16KB) | Low | More frames for large data | Default, RFC minimum |
|
||
| 32768 (32KB) | Medium | Balanced | General use |
|
||
| 65536 (64KB) | Higher | Fewer frames | Large payloads |
|
||
|
||
### System Tuning (Linux)
|
||
|
||
For high concurrency (1000+ clients), tune these kernel parameters:
|
||
|
||
```bash
|
||
# /etc/sysctl.conf or /etc/sysctl.d/99-gunicorn.conf
|
||
|
||
# Increase socket backlog for burst connections
|
||
net.core.somaxconn = 65535
|
||
net.ipv4.tcp_max_syn_backlog = 65535
|
||
|
||
# Increase network queue size
|
||
net.core.netdev_max_backlog = 65535
|
||
|
||
# Expand ephemeral port range
|
||
net.ipv4.ip_local_port_range = 1024 65535
|
||
|
||
# Allow reuse of TIME_WAIT sockets
|
||
net.ipv4.tcp_tw_reuse = 1
|
||
|
||
# Increase max open files system-wide
|
||
fs.file-max = 2097152
|
||
```
|
||
|
||
Apply with: `sudo sysctl -p`
|
||
|
||
Also increase file descriptor limits:
|
||
|
||
```bash
|
||
# /etc/security/limits.conf
|
||
* soft nofile 65535
|
||
* hard nofile 65535
|
||
```
|
||
|
||
### Docker Tuning
|
||
|
||
For Docker deployments, add these to your container or compose file:
|
||
|
||
```yaml
|
||
# docker-compose.yml
|
||
services:
|
||
gunicorn:
|
||
ulimits:
|
||
nofile:
|
||
soft: 65535
|
||
hard: 65535
|
||
sysctls:
|
||
net.core.somaxconn: 65535
|
||
```
|
||
|
||
Or in Dockerfile:
|
||
|
||
```dockerfile
|
||
# Increase file descriptor limit
|
||
RUN ulimit -n 65535
|
||
```
|
||
|
||
### Benchmark Results
|
||
|
||
Reference benchmarks using h2load with 4 Gunicorn workers in Docker (Apple M4 Pro):
|
||
|
||
| Profile | Clients | Streams | Requests/sec | Latency (mean) |
|
||
|---------|---------|---------|--------------|----------------|
|
||
| Conservative | 100 | 10 | 11,700 | 69ms |
|
||
| Conservative | 1000 | 10 | 12,750 | 441ms |
|
||
| High Concurrency | 100 | 10 | 15,000+ | 50ms |
|
||
| High Concurrency | 1000 | 10 | 21,700 | 253ms |
|
||
| High Concurrency | 2000 | 10 | 12,300 | 243ms |
|
||
|
||
!!! note
|
||
Actual performance varies based on hardware, network, and application complexity.
|
||
Always benchmark your specific workload.
|
||
|
||
## Testing HTTP/2
|
||
|
||
### Using curl
|
||
|
||
```bash
|
||
# Check HTTP/2 support
|
||
curl -v --http2 https://localhost:443/
|
||
|
||
# Force HTTP/2
|
||
curl --http2-prior-knowledge https://localhost:443/
|
||
```
|
||
|
||
### Using Python
|
||
|
||
```python
|
||
import httpx
|
||
|
||
with httpx.Client(http2=True, verify=False) as client:
|
||
response = client.get("https://localhost:8443/")
|
||
print(f"HTTP Version: {response.http_version}")
|
||
```
|
||
|
||
## Complete Example
|
||
|
||
A complete HTTP/2 example demonstrating priority and trailers is available in the
|
||
`examples/http2_features/` directory. This includes:
|
||
|
||
- **http2_app.py**: ASGI application showing priority access and trailer sending
|
||
- **test_http2.py**: Test script verifying HTTP/2 features
|
||
- **Dockerfile** and **docker-compose.yml**: Docker setup for testing
|
||
|
||
To run the example:
|
||
|
||
```bash
|
||
cd examples/http2_features
|
||
docker compose up --build
|
||
|
||
# In another terminal:
|
||
docker compose exec http2-features python /app/http2_features/test_http2.py
|
||
```
|
||
|
||
The example demonstrates:
|
||
|
||
1. **Priority access**: Reading `http.response.priority` extension in ASGI scope
|
||
2. **Response trailers**: Sending `http.response.trailers` messages
|
||
3. **Combined features**: Using both priority and trailers in one response
|
||
|
||
## RFC Compliance
|
||
|
||
Gunicorn's HTTP/2 implementation is built on the [h2 library](https://github.com/python-hyper/h2)
|
||
and complies with the following specifications:
|
||
|
||
| Feature | RFC | Status | Notes |
|
||
|---------|-----|--------|-------|
|
||
| HTTP/2 Protocol | [RFC 7540](https://tools.ietf.org/html/rfc7540) | Compliant | Core protocol support |
|
||
| HTTP/2 Semantics | [RFC 9113](https://tools.ietf.org/html/rfc9113) | Compliant | Updated HTTP/2 spec |
|
||
| HPACK Compression | [RFC 7541](https://tools.ietf.org/html/rfc7541) | Compliant | Via h2 library |
|
||
| Stream State Machine | RFC 7540 Section 5.1 | Compliant | Full state transitions |
|
||
| Flow Control | RFC 7540 Section 6.9 | Compliant | Stream and connection level |
|
||
| Stream Priority | RFC 7540 Section 5.3 | Compliant | Weight and dependency tracking |
|
||
| Frame Size Limits | RFC 7540 Section 6.2 | Compliant | Validated 16384-16777215 bytes |
|
||
| Pseudo-Headers | RFC 9113 Section 8.3 | Compliant | All required headers supported |
|
||
| `:authority` Handling | RFC 9113 Section 8.3.1 | Compliant | Takes precedence over Host |
|
||
| Response Trailers | RFC 9110 Section 6.5 | Compliant | Pseudo-headers forbidden |
|
||
| GOAWAY Handling | RFC 7540 Section 6.8 | Compliant | Graceful shutdown |
|
||
| RST_STREAM Handling | RFC 7540 Section 6.4 | Compliant | Stream reset |
|
||
| Early Hints | [RFC 8297](https://tools.ietf.org/html/rfc8297) | Compliant | 103 informational responses |
|
||
| Server Push | RFC 7540 Section 6.6 | Not Implemented | Optional feature, rarely used |
|
||
|
||
!!! note
|
||
Server Push (PUSH_PROMISE) is not implemented. This is an optional HTTP/2 feature that is
|
||
being deprecated in HTTP/3 and is rarely used in practice.
|
||
|
||
## Security Considerations
|
||
|
||
HTTP/2 introduces new attack vectors compared to HTTP/1.1. Gunicorn includes several
|
||
protections against known vulnerabilities.
|
||
|
||
### Built-in Protections
|
||
|
||
| Attack | Protection | Setting |
|
||
|--------|------------|---------|
|
||
| Stream Multiplexing Abuse | Limit concurrent streams | `http2_max_concurrent_streams` (default: 100) |
|
||
| HPACK Bomb | Header size limits | `http2_max_header_list_size` (default: 65536) |
|
||
| Large Frame Attack | Frame size limits | `http2_max_frame_size` (validated: 16384-16777215) |
|
||
| Resource Exhaustion | Flow control windows | `http2_initial_window_size` (default: 65535) |
|
||
| Slow Read (Slowloris) | Connection timeouts | `timeout` and `keepalive` settings |
|
||
|
||
### Recommended Security Settings
|
||
|
||
```python
|
||
# gunicorn.conf.py - Security-hardened HTTP/2 configuration
|
||
|
||
# Limit concurrent streams to prevent resource exhaustion
|
||
http2_max_concurrent_streams = 100
|
||
|
||
# Limit header size to prevent HPACK bomb attacks
|
||
http2_max_header_list_size = 65536 # 64KB
|
||
|
||
# Standard frame size (RFC minimum)
|
||
http2_max_frame_size = 16384
|
||
|
||
# Reasonable flow control window
|
||
http2_initial_window_size = 65535 # 64KB
|
||
|
||
# Connection timeouts to prevent slow attacks
|
||
timeout = 30
|
||
keepalive = 120
|
||
graceful_timeout = 30
|
||
|
||
# Limit request sizes
|
||
limit_request_line = 4094
|
||
limit_request_fields = 100
|
||
limit_request_field_size = 8190
|
||
```
|
||
|
||
### Additional Recommendations
|
||
|
||
1. **Use a reverse proxy**: Deploy behind nginx, HAProxy, or a cloud load balancer
|
||
for additional DDoS protection and rate limiting.
|
||
|
||
2. **Enable rate limiting**: Use your reverse proxy to limit requests per client.
|
||
|
||
3. **Monitor connections**: Watch for clients opening many streams or holding
|
||
connections open without sending data.
|
||
|
||
4. **Keep dependencies updated**: Regularly update the `h2` library for security fixes.
|
||
|
||
For more information on HTTP/2 security vulnerabilities, see:
|
||
|
||
- [Imperva HTTP/2 Vulnerability Report](https://www.imperva.com/docs/Imperva_HII_HTTP2.pdf)
|
||
- [NGINX HTTP/2 Security Advisory](https://www.nginx.com/blog/the-imperva-http2-vulnerability-report-and-nginx/)
|
||
|
||
## Compliance Testing
|
||
|
||
### h2spec
|
||
|
||
[h2spec](https://github.com/summerwind/h2spec) is the standard conformance testing tool
|
||
for HTTP/2 implementations. It tests compliance with RFC 7540 and RFC 7541.
|
||
|
||
```bash
|
||
# Install h2spec
|
||
# macOS
|
||
brew install h2spec
|
||
|
||
# Linux (download from releases)
|
||
curl -L https://github.com/summerwind/h2spec/releases/download/v2.6.0/h2spec_linux_amd64.tar.gz | tar xz
|
||
|
||
# Run against your server
|
||
h2spec -h localhost -p 8443 -t -k
|
||
|
||
# Options:
|
||
# -t Use TLS
|
||
# -k Skip certificate verification
|
||
# -S Strict mode (test SHOULD requirements)
|
||
# -v Verbose output
|
||
# -j Generate JUnit report
|
||
```
|
||
|
||
Example output:
|
||
```
|
||
Generic tests for HTTP/2 server
|
||
1. Starting HTTP/2
|
||
✓ Sends a client connection preface
|
||
...
|
||
|
||
Hypertext Transfer Protocol Version 2 (HTTP/2)
|
||
3. Starting HTTP/2
|
||
3.5. HTTP/2 Connection Preface
|
||
✓ Sends invalid connection preface
|
||
...
|
||
|
||
94 tests, 94 passed, 0 skipped, 0 failed
|
||
```
|
||
|
||
### nghttp2 Tools
|
||
|
||
[nghttp2](https://nghttp2.org/) provides useful debugging tools:
|
||
|
||
```bash
|
||
# Install nghttp2
|
||
# macOS
|
||
brew install nghttp2
|
||
|
||
# Linux
|
||
apt-get install nghttp2-client
|
||
|
||
# Test HTTP/2 connection
|
||
nghttp -v https://localhost:8443/
|
||
|
||
# Benchmark with h2load
|
||
h2load -n 1000 -c 10 https://localhost:8443/
|
||
```
|
||
|
||
### Online Testing
|
||
|
||
For public servers, you can use online tools:
|
||
|
||
- [KeyCDN HTTP/2 Test](https://tools.keycdn.com/http2-test)
|
||
- [HTTP/2 Check](https://http.dev/2/test)
|
||
|
||
## See Also
|
||
|
||
- [Settings Reference](../reference/settings.md#http2_max_concurrent_streams) - All HTTP/2 settings
|
||
- [ASGI Worker](../asgi.md) - ASGI worker with HTTP/2 support
|
||
- [Deploy](../deploy.md) - General deployment guidance
|