Fixing IPv6 Connectivity Issues
This guide helps troubleshoot and fix IPv6 connectivity problems that can affect SSL Labs testing and external IPv6 access to EAS Station.
Table of Contents
Overview
EAS Station uses Docker Compose with an IPv6-enabled network (fd00:ea:1::/64). While this enables modern dual-stack networking, it can cause issues when:
- SSL Labs cannot reach your IPv6 site - The AAAA DNS record points to an IPv6 address, but the server isn't responding on that address
- nginx fails to connect to the backend - Docker DNS returns IPv6 addresses that the Flask backend doesn't bind to
- Intermittent connection failures - nginx tries IPv6 first, fails, then retries on IPv4
Common Symptoms
Symptom 1: SSL Labs IPv6 Test Fails
SSL Labs reports "Unable to connect to the server" for IPv6 but IPv4 works fine.
Cause: Your DNS has an AAAA record, but nginx isn't properly publishing on IPv6, OR external IPv6 traffic isn't reaching Docker.Symptom 2: nginx Upstream Connection Refused
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://[fd00:ea:1::3]:5000/"
Cause: nginx resolved the backend hostname to an IPv6 address, but the Flask/Gunicorn app only binds to IPv4 (0.0.0.0:5000).Symptom 3: Intermittent 499/502 Errors
Requests randomly fail with 499 (client closed connection) or 502 (bad gateway).
Cause: nginx tries the IPv6 address first, times out, and the client gives up before the IPv4 fallback succeeds.Root Cause Analysis
The Technical Issue
Docker Compose creates an IPv6-enabled network with both IPv4 and IPv6 subnets:
networks:
eas-network:
enable_ipv6: true
ipam:
config:
- subnet: 172.20.0.0/16 # IPv4
- subnet: fd00:ea:1::/64 # IPv6
When nginx resolves app:5000 using Docker's embedded DNS (127.0.0.11), it gets both IPv4 and IPv6 addresses. The resolver 127.0.0.11 ipv6=off; directive only affects runtime variable resolution—not statically configured upstream blocks.
Since Gunicorn binds to 0.0.0.0:5000 (IPv4 only), connections to the IPv6 address fail with "Connection refused."
The Fix (Already Applied)
The nginx configuration uses variable-based proxy_pass directives that force runtime DNS resolution, which does respect the ipv6=off setting:
Force IPv4-only resolution for backend connections
resolver 127.0.0.11 ipv6=off valid=30s;map $host $backend_server {
default "app:5000";
}
location / {
set $backend http://$backend_server;
proxy_pass $backend; # Resolved at runtime, respects ipv6=off
}
Diagnostic Steps
Step 1: Run the IPv6 Diagnostics Script
./debug-ipv6-server.sh
This script checks:
- IPv6 system status
- IPv6 addresses on interfaces
- IPv6 routes
- Docker IPv6 configuration
- Firewall rules
- Listening ports
Step 2: Check nginx Logs for IPv6 Errors
docker compose logs nginx | grep -i "connect() failed"
Look for errors mentioning IPv6 addresses like [fd00:ea:1::X].
Step 3: Verify Backend Binding
docker compose exec app netstat -tlnp | grep 5000
Should show:
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN
If it shows :::5000, the backend is listening on IPv6 (good). If only 0.0.0.0:5000, it's IPv4 only.
Step 4: Test DNS Resolution Inside nginx
docker compose exec nginx nslookup app
Check if it returns both IPv4 and IPv6 addresses.
Solutions
Solution 1: Use Variable-Based Proxy Pass (Recommended)
This is already implemented in the current nginx.conf. The configuration uses:
resolver 127.0.0.11 ipv6=off valid=30s;map $host $backend_server {
default "app:5000";
}
location / {
set $backend http://$backend_server;
proxy_pass $backend;
}
Why it works: Using a variable in proxy_pass forces nginx to resolve the hostname at request time using the configured resolver, which has ipv6=off.Solution 2: Disable IPv6 on the Docker Network
If you don't need IPv6 at all, disable it in docker-compose.yml:
networks:
eas-network:
enable_ipv6: false # Changed from true
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
# Remove IPv6 subnet
Pros: Simplest solution
Cons: Breaks IPv6 connectivity for external clientsSolution 3: Make Gunicorn Listen on IPv6
Modify the Dockerfile CMD to bind to both IPv4 and IPv6:
CMD ["sh", "-c", "gunicorn --bind [::]:5000 --workers ${MAX_WORKERS:-2} ..."]
Note: Binding to [::] on Linux typically also accepts IPv4 connections (dual-stack).Pros: Full IPv6 support internally
Cons: May require additional testingSolution 4: Fix External IPv6 Connectivity
If SSL Labs can't reach your IPv6 address:
- Verify your server has a public IPv6 address:
ip -6 addr show scope global
- Check IPv6 routing:
ip -6 route show default
- Verify firewall allows IPv6 traffic:
sudo ip6tables -L INPUT -n | grep -E "(80|443)"
- Test from an external IPv6 host:
curl -6 -v https://yourdomain.com/
- Ensure Docker publishes ports on IPv6:
ports:
- "80:80" # Binds to both IPv4 and IPv6 by default
- "443:443"
Verification
Test 1: Internal Backend Connectivity
docker compose exec nginx curl -v http://app:5000/health
Should connect without IPv6 errors.
Test 2: External IPv4 Access
curl -4 -v https://yourdomain.com/health
Test 3: External IPv6 Access
curl -6 -v https://yourdomain.com/health
Test 4: SSL Labs
Visit https://www.ssllabs.com/ssltest/ and test your domain. Both IPv4 and IPv6 should pass.
Related Documentation
- HTTPS_SETUP.md - SSL certificate configuration
- debug-ipv6-server.sh - IPv6 diagnostics script
Last Updated: 2025-11-26
This document is served from docs/troubleshooting/FIX_IPV6_CONNECTIVITY.md in the EAS Station installation.