DMIT ships dual-stack: the IPv6 leak that breaks your IPv4 tunnel
DMIT VPS plans include IPv6 by default, and a misconfigured stack will leak traffic past your IPv4-routed residential proxy. The lockdown playbook, with the verification commands that catch the leak before users do.
Most VPS providers ship dual-stack by default. DMIT hands you an IPv4 address and a routed IPv6 /64 in the same provisioning email. If your tunnel design assumes IPv4-only egress — and most residential-proxy chains do — that v6 /64 is a leak waiting to happen. Xray will happily accept inbound connections over IPv6, egress directly to v6-enabled destinations bypassing your residential proxy, and the client gets a tunnel that looks correct but exposes the wrong exit IP for any v6-reachable site.
This is not theoretical. We've seen the leak in our own deployments. The fix is short and the verification is concrete. This article documents both.
The short version
- Disable IPv6 at the kernel for
all,default, andlointerfaces via/etc/sysctl.d/. - Persist with
sysctl -p. - Add ip6tables drop rules as belt-and-suspenders.
- Set
AddressFamily inetinsshd_configso SSH never binds v6. - Force Xray DNS to
UseIPv4anddomainStrategy: UseIPv4on egress. - Verify with three commands on the server and two from the client.
If you're starting from a fresh DMIT box and run the block in the next section before anything else, the leak never appears. If you're hardening a deployed box, the same block fixes it, but you should also audit logs to see whether v6 egress already happened.
Why DMIT specifically
Two things about DMIT make the IPv6 leak more likely than on, say, AWS Lightsail:
- DMIT defaults to v6 enabled and routed. The provisioning email gives you a /64 you didn't ask for. Lightsail makes you opt in to v6 — most users never enable it, so the leak surface doesn't exist.
- DMIT's stock Debian/Ubuntu image doesn't apply any sysctl hardening. Out of the box,
net.ipv6.conf.all.disable_ipv6is0. The interface accepts v6 router advertisements, configures a global address, and routes packets without you doing anything.
The combination — v6 on, v6 routable, no kernel guardrails — means anything you install that listens on [::]:port or egresses to a v6 destination will use it. Xray is the most common offender because its default config is dual-stack.
How the leak actually happens
The leak has three flavors and you can hit any of them independently:
Flavor 1 — inbound v6. Xray's inbound binds to [::]:443 if you set listen: 0.0.0.0 in the config but the kernel binds both stacks. A client with a v6-routable IP can connect over v6, hit your server's v6 interface, and the connection is logged from the client's real v6 IP — not the carrier-grade-NAT'd v4 you expected. This breaks any logging-based or IP-allowlist-based defense you've put in front of the inbound.
Flavor 2 — outbound v6 bypassing the residential proxy. This is the worst one. You've wired Xray to forward outbound traffic through a Webshare residential proxy. The Webshare proxy is IPv4-only. When Xray tries to reach a destination that has AAAA records (most major destinations do), it resolves to v6, picks the v6 outbound, and the residential-proxy rule never fires. The destination sees your DMIT v6 IP. Your "residential exit" is leaking the actual datacenter IP for half your traffic.
Flavor 3 — system process v6 egress. Even with Xray locked to v4, system processes (apt, journald, unattended-upgrades, chrony) may egress over v6 if the kernel route table prefers it. This isn't a user-traffic leak but it's a server-identity leak — the v6 addresses your server contacts are correlated with your v4 in passive monitoring.
All three are fixed by the same lockdown.
The lockdown block
Run as root on the DMIT box.
# 1. Disable IPv6 at the kernel for all current and future interfaces
cat > /etc/sysctl.d/99-disable-ipv6.conf <<'EOF'
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
# 2. Apply immediately
sysctl -p /etc/sysctl.d/99-disable-ipv6.conf
# 3. Belt-and-suspenders: ip6tables drops everything
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP
# persist with iptables-persistent or netfilter-persistent depending on distro
apt-get install -y iptables-persistent
netfilter-persistent save
# 4. Force SSH to bind v4 only
sed -i 's/^#\?AddressFamily .*/AddressFamily inet/' /etc/ssh/sshd_config
sshd -t && systemctl restart ssh
Four steps. The sysctl block is the load-bearing piece — once disable_ipv6 = 1 is enforced on all + default, the kernel won't bring v6 up on any interface, present or future, and processes can't bind v6 sockets because there are none.
The ip6tables drop rules look redundant. They're not. If a future operator re-enables v6 by editing sysctl and forgets to flush ip6tables, traffic still drops. If a process explicitly opens an AF_INET6 socket and somehow gets one, the firewall blocks it. The cost is zero performance overhead (the kernel evaluates v6 chains only when v6 packets exist, which is never) and the benefit is the leak doesn't come back when someone goofs.
Xray-specific lockdown
Even with the kernel v6 off, the Xray config itself should be explicit about v4-only behavior. Two places to set this:
{
"dns": {
"servers": [
{ "address": "https+local://1.1.1.1/dns-query", "queryStrategy": "UseIPv4" },
{ "address": "https+local://8.8.8.8/dns-query", "queryStrategy": "UseIPv4" }
],
"queryStrategy": "UseIPv4"
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": { "domainStrategy": "UseIPv4" }
}
]
}
Two UseIPv4 settings, one in DNS and one in the freedom outbound. Belt and suspenders again — even if the kernel were somehow v6-enabled, Xray would refuse to resolve AAAA records or egress over v6.
For the webshare outbound (HTTP proxy), v4-only is implicit because the upstream proxy server is v4-only. Don't add domainStrategy there; it's not the right place.
Verification
Three commands on the server:
# 1. Kernel-level: all three should return 1
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
cat /proc/sys/net/ipv6/conf/default/disable_ipv6
cat /proc/sys/net/ipv6/conf/lo/disable_ipv6
# 2. Interface-level: eth0 should have NO inet6 line
ip -6 addr show
# 3. Route table: empty for v6
ip -6 route show
Expected: three 1s, no inet6 addresses except possibly a link-local on the loopback (which can't egress), no v6 routes.
Two commands from a client (connected through the tunnel):
# IPv4 exit IP — should be Webshare residential IP, never the DMIT IP
curl -4 https://ifconfig.co
# IPv6 exit attempt — should fail with "couldn't connect" or "no route"
curl -6 https://ifconfig.co
If curl -6 succeeds and returns an IP, your tunnel is leaking v6. Either the server is still v6-enabled (recheck the sysctl values) or the client itself is egressing v6 directly without going through the tunnel — check the client's v6 routes or the Reality inbound config.
The combination — curl -4 returns Webshare, curl -6 returns nothing — is the only acceptable state.
Past mistake worth not repeating
In May 2026 we deployed a DMIT Tokyo node with v6 disabled at the kernel but forgot to set AddressFamily inet in sshd. SSH bound [::]:22. UFW only had v4 allow rules. ip6tables was default-accept on INPUT. The combination meant SSH was reachable over v6 from anywhere, with no firewall in front. Nobody compromised it, but the audit caught it: the server identity was leaking via SSH banner grabs from v6 scanners.
Fix: AddressFamily inet in sshd_config, restart sshd, verify with ss -tlnp | grep ssh showing only 0.0.0.0:22022 and no [::]:22022. Lesson: the kernel disable is necessary but not sufficient. Daemons that opened sockets before the disable took effect, or that explicitly bind v6 in their config, can still listen even when v6 is "off." Audit ss -tlnp and ss -ulnp after every config change.
The same risk applies to any other daemon you install — nginx, postfix, apache, anything with a default-dual-stack listener. Check ss -tlnp for [::]: entries; if you see any, fix the daemon's config to bind v4 only or accept the audit risk.
When IPv6 is the right answer
Two cases.
End-user IPv6 reachability. If your tunnel users are on v6-only or v6-preferred networks (some mobile carriers, some institutional networks), v4-only egress works but the inbound to your DMIT box should accept v6 to keep the path open. The fix is not "re-enable v6 globally"; it's bind Xray's inbound to a v6 address explicitly while keeping all outbound and management traffic v4. Niche. Most deployments don't need it.
You actually want a v6 exit. Rare. The only case is if the destination service is v6-only (some research networks, some IPv6-only CDN endpoints) and going through v4-to-v6 NAT at the residential proxy isn't acceptable. In that case, set up a parallel v6 outbound with its own routing rule, leave v4 going through Webshare, and accept that the v6 exit IP is your DMIT identity.
Both cases are corner cases. The default for a personal or small-team tunnel is v4 everywhere, v6 dead at the kernel, and Webshare doing the IP-reputation work on v4.
The complete hardening sequence in order
If you're standing up a new DMIT box and want the v6 lockdown to be part of the initial deploy:
- SSH in with the root password from the provisioning email.
- Swap to key auth, change SSH port, set
AddressFamily inetat the same time. - Apply the sysctl block.
sysctl -p. - Install
iptables-persistent, set ip6tables policies to DROP, save. - Install UFW for v4 rules.
- Install chrony (Reality requires accurate time).
- Install Xray with the
UseIPv4config blocks above. - Start Xray.
- Verify with
ip -6 addr show(empty),ss -tlnp | grep ":::"(empty),curl -6 https://ifconfig.cofrom a client (fails).
Order matters because steps 1-3 close the v6 surface before step 7 brings up a daemon that would otherwise bind v6. Reversing them creates a window where Xray is dual-stack-listening before the kernel locks v6.
For the rest of the deploy — Reality keypair, SNI selection, subscription wiring — see provisioning a DMIT Tokyo node from zero.
What it doesn't fix
The v6 lockdown closes one leak surface. It does nothing about:
- DNS leaks — even on v4-only, if the client uses its local resolver, the destination domain leaks to whoever runs that resolver. Force the client to DoH at the application layer.
- WebRTC leaks — browser WebRTC can reveal the client's local IP regardless of tunnel state. Disable in the client browser or use a hardened profile.
- Application metadata — User-Agent strings, accept-language headers, TLS fingerprints (JA3/JA4), behavior patterns. The IP is one identifier; everything else still moves with the client.
For a fuller leak audit, run through the checks in auditing network exposure with nmap on the server side and the leak-test suite from browser fingerprint hardening on the client side.
If you're deploying multiple DMIT nodes and want a consistent v6-lockdown baseline that doesn't drift between boxes, that's the kind of thing we systematize at /services.
Liked this? Get one a week.
One technical post per week — same depth, no spam.
The Server Is Up but Nobody Can Connect: A Layered Debugging Playbook
Chaining DMIT Tokyo with a Webshare residential exit: the two-hop egress pattern
Provisioning a DMIT Tokyo node from zero: 45 minutes to a working CN2 GIA tunnel
We do this kind of work for hire.
Network architecture review, self-hosted privacy stacks, zero-trust corporate VPNs.