- JavaScript 57.5%
- Go 32.5%
- HTML 9.3%
- Python 0.6%
- Makefile 0.1%
| .github/workflows | ||
| cmd/ipv6sx | ||
| deploy | ||
| docs | ||
| internal | ||
| scripts | ||
| web | ||
| .gitattributes | ||
| .gitignore | ||
| DESIGN.md | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
ipv6.sx
A small IPv4/IPv6 connectivity checker written in Go. One binary serves the English interface, JSON/XML/text endpoints, statistics, and 88×31 web buttons. The interface uses plain HTML, CSS, and JavaScript, with no frontend build step, external fonts, analytics scripts, or runtime geolocation service.
The public deployment uses three origins:
| Host | DNS | Purpose |
|---|---|---|
ipv6.sx |
A + AAAA | Website and the connection family selected by the browser |
v4.ipv6.sx |
A only | IPv4 address and latency probes |
v6.ipv6.sx |
AAAA only | IPv6 address and latency probes |
Use direct DNS records, without a CDN proxy. Serve each hostname with its own single-host TLS certificate so the browser cannot reuse an HTTP/2 connection across the probe origins. See deployment.
Run locally
Install Go 1.25 or newer, then run:
go run ./cmd/ipv6sx
Open http://localhost:8080. The local defaults probe 127.0.0.1 and ::1;
this checks your local loopback setup, not your ISP's IPv6 connectivity. The
server must listen on both families for both local probes to succeed. Opening
the site through another hostname requires changing IPV6SX_SITE_URL to match.
For an entirely loopback setup, the content security policy permits the configured
HTTP(S) schemes for probes and images, because browsers cannot match literal IPv6
addresses in CSP host sources. Production uses DNS hostnames and restricts those
requests to the configured origins.
curl http://localhost:8080/text
curl http://localhost:8080/json
curl http://localhost:8080/xml
curl http://localhost:8080/api/stats
Build and check the application:
make build
make check
make check runs go test -race ./..., go vet ./..., and the dependency-free
JavaScript tests with Node.js 22 or newer. Node is only needed for these tests;
it is not needed to build or run the server. The binary is bin/ipv6sx.
What it measures
- The address, IP family, and optional country/ASN for the request reaching the main origin; this is the displayed default family.
- Connectivity to the IPv4-only and IPv6-only origins independently.
- Six timed HTTP probes per family after one discarded warm-up, alternating which family runs first. A failed probe counts as a failure, never as zero ms.
- Mean latency, variability, and the difference between IPv6 and IPv4 latency. These are browser HTTP timings, not ICMP ping. Browser scheduling, TLS, proxies, VPNs, and the server's location affect the result.
- Aggregate results by UTC day, country, and ASN. Counts represent submitted tests, not unique people or devices, and repeated tests count again.
- Filter
/statsby country, ASN, or both and an inclusive UTC date range. The daily chart compares IPv6 availability with IPv6 used by default; missing observation days remain gaps. Filter URLs and JSON downloads preserve the selection.
Country and ASN come from optional local MaxMind-compatible MMDB files. Without them, connectivity and timing still work; attribution is shown as unknown. The application does not make geolocation requests to third parties.
See measurement and statistics semantics for precise definitions and limits, and the HTTP API for response examples.
Configuration
Configuration is read from environment variables at startup. The application
does not load .env files automatically.
| Variable | Default | Meaning |
|---|---|---|
IPV6SX_LISTEN |
:8080 |
HTTP listener; use 127.0.0.1:8080 behind local nginx |
IPV6SX_SITE_URL |
http://localhost:8080 |
Main public origin |
IPV6SX_IPV4_URL |
http://127.0.0.1:8080 |
IPv4-only public origin |
IPV6SX_IPV6_URL |
http://[::1]:8080 |
IPv6-only public origin |
IPV6SX_STATS_FILE |
data/stats.json |
Aggregate JSON file; an empty value uses memory only |
IPV6SX_RETENTION_DAYS |
90 |
Number of UTC days retained, from 1 through 3660 |
IPV6SX_TRUSTED_PROXIES |
empty | Comma-separated CIDRs allowed to provide X-Real-IP |
IPV6SX_GEO_COUNTRY_DB |
empty | Optional Country MMDB path |
IPV6SX_GEO_ASN_DB |
empty | Optional ASN MMDB path |
Origins include the scheme and optional port, with no path, trailing slash, or query. Use HTTPS for all three origins in production. Set the main origin to exactly the address visitors use; the browser's probe requests use the configured origin policy. Providing an invalid database path fails startup rather than silently disabling an explicitly configured database.
Only one process may own a statistics file. The store persists aggregates,
without raw IP addresses, tokens, or individual timing samples. Its JSON format
is internal; use /api/stats to consume statistics. Your reverse proxy or host
may have separate access/error logging policies.
Buttons and embedding
/embed provides copyable HTML for a static webring button and an IPv6
connectivity indicator. The latter layers an IPv6-only image over a fallback
and needs no JavaScript. /badge.svg shows the family of the request fetching
that image; /assets/webring.svg is a static link button.
The live badge reads IPv6 capable in green for IPv6 requests and IPv6 not
detected otherwise. /assets/check-ipv6.svg adds a static Check your IPv6
connectivity! button. All buttons are 88×31 and have copyable HTML on /embed.
The indicator describes the visitor's ability to load the IPv6-only image. Image blocking, browser policies, or network filtering can prevent it loading. Loading a badge does not submit a test to the statistics store.
Branding
The complete A1 brand kit lives in web/assets/brand/,
including vector masters, light/dark variants, PNG exports, favicons, app icons,
social images, 88×31 buttons, and the Spanish usage guide. Browse its gallery
at /assets/brand/. The gallery styles are external so it works with the site's
content security policy; the supplied image assets are unchanged.
All four pages share the logo and icon/social metadata in web/brand.html.
The header uses logo-light.svg because the interface has a fixed light
background, while the SVG favicon follows the browser's color scheme. Social
image URLs use IPV6SX_SITE_URL, including on self-hosted installations.
The manifest retains its relative icon paths and root application scope.
/assets/favicon.svg and /assets/webring.svg remain available at their existing
URLs with the new artwork. The live connectivity badges keep their protocol
status labels. All assets are embedded in the Go binary: rebuild it when
updating the brand kit. No frontend build or external asset service is needed.
Small nginx deployment example
The checked-in nginx configuration contains separate virtual hosts for the main site and the two probe hosts, plus a shared proxy snippet. The Go service listens on loopback:
IPV6SX_LISTEN=127.0.0.1:8080
IPV6SX_SITE_URL=https://ipv6.sx
IPV6SX_IPV4_URL=https://v4.ipv6.sx
IPV6SX_IPV6_URL=https://v6.ipv6.sx
IPV6SX_TRUSTED_PROXIES=127.0.0.1/32,::1/128
IPV6SX_STATS_FILE=/var/lib/ipv6sx/stats.json
nginx overwrites the supplied address when forwarding each request:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
Use the complete deployment instructions for DNS, certificates, systemd, persistent data, and verification commands. The snippets above alone do not configure the family-specific listeners or TLS.