DEV Community

IPYE
IPYE

Posted on AI-assisted

IPv6 for Web Servers: Dual-Stack nginx and Apache, and How to Test It

Disclosure up front: I run IPYE, a company that leases IP space. It gets one short paragraph at the end; the rest is plain networking.

Adding IPv6 to a web server is mostly four small jobs: put an address on the host, make the web server listen on it, publish an AAAA record, and let the traffic through the firewall. Each step has a common mistake, and the worst one, publishing the AAAA record before the other steps work, breaks the site for IPv6 visitors. This article walks through nginx and Apache and then shows how to test each layer separately. Examples use 2001:db8:100::10 for the server and www.example.com for the site.

Step 1: the address and route

Confirm the host has the address and a default route before touching the web server:

ip -6 addr show dev eth0
ip -6 route show default
ping -6 -c 3 2001:db8:100::1     # the gateway
Enter fullscreen mode Exit fullscreen mode

If the provider gave you a /64 or larger, configure 2001:db8:100::10 with a prefix length that matches the subnet (typically /64) and the gateway they specify.

Step 2: nginx

nginx listens per address family, so you add an IPv6 listen directive next to each IPv4 one:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.example.com;

    ssl_certificate     /etc/ssl/example/fullchain.pem;
    ssl_certificate_key /etc/ssl/example/privkey.pem;
    ...
}
Enter fullscreen mode Exit fullscreen mode

Details that cause trouble:

  • On Linux, listen [::]:80; by default creates an IPv6-only socket (the ipv6only=on default). That is what you want, since listen 80; already covers IPv4. Writing [::]:80 ipv6only=off and also listen 80; conflicts and nginx reports "address already in use".
  • The ipv6only parameter can be set only once for a given port, on the first listen that defines it. Parameters such as default_server apply per listen directive, so if you mark the IPv4 listener of a block as the default, mark its IPv6 listener too.
  • Access rules accept IPv6 syntax directly: allow 2001:db8:200::/48;. A rule list that only has IPv4 entries silently allows or denies IPv6 clients according to the final deny all, so review each.
  • Behind a proxy or load balancer, set real_ip_header and set_real_ip_from with both IPv4 and IPv6 ranges of the proxy, or your logs will show the proxy's address.

Check and reload with nginx -t && systemctl reload nginx.

Step 3: Apache

Apache's Listen directive without an address, such as Listen 80, binds to all interfaces. On Linux that normally includes IPv6, with IPv4 connections handled through the same dual-stack socket. To be explicit, or to bind specific addresses, give them separately:

Listen 192.0.2.10:443
Listen [2001:db8:100::10]:443

<VirtualHost 192.0.2.10:443 [2001:db8:100::10]:443>
    ServerName www.example.com
    SSLEngine on
    SSLCertificateFile    /etc/ssl/example/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/example/privkey.pem
</VirtualHost>
Enter fullscreen mode Exit fullscreen mode

If you use <VirtualHost *:443>, the wildcard already matches IPv6. Address-based access rules use Require ip 2001:db8:200::/48. Validate with apachectl configtest.

Step 4: the firewall

Open the same ports in the IPv6 ruleset. It is common to see iptables rules for 80 and 443 with nothing in ip6tables, or an nftables ip table without an ip6 or inet equivalent. Also keep ICMPv6 (Packet Too Big, in particular) working, otherwise some clients will connect and then hang on large responses.

Step 5: test before publishing the AAAA record

Do not add DNS yet. Test the server by address, with the right hostname, so TLS and virtual hosts still match:

ss -tlnp | grep -E ':(80|443)\b'
curl -6 -I --resolve www.example.com:443:[2001:db8:100::10] https://www.example.com/
Enter fullscreen mode Exit fullscreen mode

ss should show a listener on [::]:443 (or the specific IPv6 address). --resolve makes curl connect to the given address while still sending www.example.com in the SNI field and Host header; the brackets are required for IPv6 literals. Test from a different network than the server, since local tests can succeed through the loopback path and miss firewall problems.

Step 6: publish the AAAA record

Add the record with a modest TTL so you can pull it back quickly:

www.example.com.  300  IN  AAAA  2001:db8:100::10
Enter fullscreen mode Exit fullscreen mode

Then query it and test through normal resolution:

dig +short AAAA www.example.com
curl -6 -sS -o /dev/null -w '%{remote_ip} %{http_code}\n' https://www.example.com/
curl -4 -sS -o /dev/null -w '%{remote_ip} %{http_code}\n' https://www.example.com/
Enter fullscreen mode Exit fullscreen mode

Both should return a success code. The two address families should serve identical content: run both checks and compare. Also check that the certificate is the same, since a stray default server on only one family can present a different certificate.

Things that break only on IPv6

  • Applications that store or validate IP addresses. Database columns sized for 15 characters, regular expressions that accept only dotted quads, and libraries that mis-parse a colon as a port separator all show up once IPv6 clients arrive. A full textual IPv6 address can be 39 characters long, and forms with an embedded IPv4 address reach 45.
  • Rate limiting and bans. One IPv6 user commonly has a /64 or more, so limits keyed on a single /128 are easy to evade. Aggregate by /64 where that fits your policy.
  • Logging and analytics. Log parsers and fail2ban rules need IPv6 patterns. Make sure ban actions apply to ip6 firewall tables.
  • Reverse DNS. Mail servers and some services check PTR records; delegate and set ip6.arpa entries for addresses that send mail.
  • Hard-coded addresses in configuration or allow lists.

Rolling back

If IPv6 users have trouble, remove the AAAA record. Clients fall back to IPv4 once cached records expire. A low TTL during rollout shortens that window, and you can raise it again once the service has been stable.

About IPYE

IPYE (https://my.ipye.com) offers leased IPv4, IPv6, and ASN space. The first year of a /48 IPv6 block is free, and IPYE handles LOA, ROA, rDNS, and WHOIS. Locations: Los Angeles, New York, Ashburn, Salt Lake City, London, Amsterdam, and Frankfurt. Questions: ipye@turnel.com.

Disclosure: I run IPYE

Top comments (0)