DNS Configuration Guide¶
This guide explains how to configure DNS records for MoaV.
Table of Contents¶
- Do I Need a Domain?
- Domainless Mode
- Domain Setup
- Minimum Setup (Without DNS Tunnels)
- Full Setup (With DNS Tunnels)
- Provider-Specific Instructions
- Cloudflare
- AWS CloudFront (Alternative CDN)
- Namecheap
- Google Domains / Squarespace
- Hetzner DNS
- Home Servers & Raspberry Pi
- Port Forwarding
- Dynamic DNS (DDNS)
- DuckDNS (Free)
- Cloudflare DDNS (Own Domain)
- Home Server Tips
- Verification
- Common Issues
- Domain Acquisition Tips
Do I Need a Domain?¶
No. MoaV can run without a domain in domainless mode. A domain unlocks more protocols, but several work with just an IP address.
| Protocol | Requires Domain | Port |
|---|---|---|
| Reality (VLESS) | No | 443/tcp |
| XHTTP (VLESS+XHTTP+Reality) | No | 2096/tcp |
| WireGuard | No | 51820/udp |
| WireGuard (wstunnel) | No | 8080/tcp |
| AmneziaWG | No | 51821/udp |
| Telegram MTProxy (telemt) | No | 993/tcp |
| Admin Dashboard | No | 9443/tcp |
| Conduit (Psiphon donation) | No | — |
| Snowflake (Tor donation) | No | — |
| Trojan | Yes | 8443/tcp |
| Hysteria2 | Yes | 443/udp |
| TrustTunnel | Yes | 4443/tcp+udp |
| CDN (VLESS+WebSocket) | Yes (Cloudflare) or No (CloudFront) | 2082/tcp |
| dnstt (DNS tunnel) | Yes (NS records) | 53/udp |
| Slipstream (QUIC-over-DNS) | Yes (NS records) | 53/udp |
| XDNS (mKCP DNS tunnel) | Yes (NS records) | 53/udp |
Domain-dependent protocols need a valid TLS certificate (via Let's Encrypt) or NS delegation, which both require a domain.
Domainless Mode¶
Leave DOMAIN= empty in your .env file. MoaV automatically detects this and runs only protocols that work without a domain:
- Reality — VLESS with TLS camouflage (uses
REALITY_TARGETlikedl.google.cominstead of your own domain) - XHTTP — VLESS+XHTTP+Reality via Xray-core (same TLS camouflage, different transport)
- WireGuard — Full VPN, direct UDP or tunneled over WebSocket (TCP) when UDP is blocked
- AmneziaWG — DPI-resistant WireGuard with packet-level obfuscation
- Telegram MTProxy — Direct Telegram access via fake-TLS, no VPN needed
- Admin Dashboard — Web UI with self-signed certificate
- Conduit / Snowflake — Bandwidth donation (optional)
This is ideal for: - Raspberry Pi or home servers without a registered domain - Quick deployments when you can't register a domain - Environments where only VPN-style protocols are needed
You can upgrade to a full domain setup later — just set DOMAIN= in .env and run moav bootstrap.
Port Forwarding (Domainless)¶
If running on a home network, forward these ports on your router:
| Port | Protocol | Service |
|---|---|---|
| 443/tcp | TCP | Reality (VLESS) |
| 2096/tcp | TCP | XHTTP (VLESS+XHTTP+Reality) |
| 51820/udp | UDP | WireGuard |
| 8080/tcp | TCP | wstunnel (WireGuard over WebSocket) |
| 51821/udp | UDP | AmneziaWG |
| 993/tcp | TCP | Telegram MTProxy |
| 9443/tcp | TCP | Admin Dashboard |
No port 80 needed — domainless mode doesn't use Let's Encrypt.
Domain Setup¶
If you have a domain, you unlock all 13+ protocols. How many DNS records you need depends on which features you enable.
Minimum Setup (Without DNS Tunnels)¶
If you don't need DNS tunnels (dnstt / Slipstream), you only need one record:
This enables: Reality, Trojan, Hysteria2, TrustTunnel, CDN mode, and all domainless protocols.
Full Setup (With DNS Tunnels)¶
Step 1: Main A Record¶
Step 2: DNS Server A Record¶
This creates dns.yourdomain.com pointing to your server (used as the nameserver for tunnel subdomains).
Step 3: NS Delegation for dnstt¶
This tells DNS resolvers that queries for *.t.yourdomain.com should be sent to dns.yourdomain.com (your server). Used by dnstt.
Step 4: NS Delegation for Slipstream (Optional)¶
Same concept as dnstt, but for the Slipstream QUIC-over-DNS tunnel. Slipstream is 1.5-5x faster than dnstt. Only needed if ENABLE_SLIPSTREAM=true.
Step 5: NS Delegation for XDNS (Recommended)¶
XDNS is a DNS tunnel protocol that uses Xray-core's FinalMask technology to encode VLESS traffic in DNS-like packets via mKCP transport, with per-user authentication. It requires a FinalMask-aware client (Happ, Xray CLI). For broader client-ecosystem support, dnstt and Slipstream are enabled by default (standalone client binaries on 25+ platforms).
Note: XDNS, dnstt, and Slipstream all use port 53. Only one group can own port 53 at a time. dnstt+Slipstream share dns-router by default (
PORT_DNS=53). To switch to XDNS instead, runmoav switch-dns xdns. To combine dnstt+Slipstream, runmoav switch-dns dnstt+slipstream(default). Seemoav switch-dns listfor current state.
Optional: IPv6 Support¶
If your server has IPv6, you can also add an AAAA record for the nameserver:
More Info: For detailed dnstt documentation, see the official dnstt guide.
Summary of All DNS Records¶
| Record | Name | Value | Proxy | Purpose | Required? |
|---|---|---|---|---|---|
| A | @ |
Server IP | DNS only | Main domain (Trojan, Hysteria2, Reality) | Yes |
| A | dns |
Server IP | DNS only | Nameserver for DNS tunnels | Only for dnstt/Slipstream/XDNS/XDNS |
| NS | t |
dns.domain.com |
— | dnstt tunnel subdomain | Only for dnstt |
| NS | s |
dns.domain.com |
— | Slipstream tunnel subdomain | Only for Slipstream |
| NS | x |
dns.domain.com |
— | XDNS tunnel subdomain | Only for XDNS |
| A | cdn |
Server IP | Proxied | CDN-fronted VLESS | Only for CDN mode |
| A | www |
Server IP | Proxied | CDN stealth connect address | Optional (CDN stealth) |
| A | grafana |
Server IP | Proxied | Grafana via CDN | Optional (monitoring) |
Provider-Specific Instructions¶
Cloudflare¶
- Log into Cloudflare Dashboard
- Select your domain
- Go to DNS → Records
- Add records:
Important: Set proxy status to "DNS only" (gray cloud) for most records. Only CDN-related records should be "Proxied" (orange cloud).
| Type | Name | Content | Proxy status |
|---|---|---|---|
| A | @ | YOUR_IP | DNS only |
| A | dns | YOUR_IP | DNS only |
| NS | t | dns.yourdomain.com | — |
| NS | s | dns.yourdomain.com | — |
| A | cdn | YOUR_IP | Proxied (orange cloud) |
| A | www | YOUR_IP | Proxied (orange cloud) |
| A | grafana | YOUR_IP | Proxied (orange cloud) |
The
cdn,www, andgrafanarecords are optional: -cdn— Required if you want CDN-fronted VLESS (ENABLE_CDN=trueor CDN_SUBDOMAIN set) -www— Recommended for CDN stealth. Used as the CDN connect address so DNS queries don't reveal the "cdn" subdomain to DPI. SetCDN_ADDRESS=www.yourdomain.comin.env-grafana— Only needed if you want faster Grafana loading via CDN (see Monitoring Guide)All other records must be DNS only (gray cloud).
CDN Origin Rule (Required for CDN Mode)¶
If you added the cdn record above, you must also create an Origin Rule to redirect traffic to port 2082. By default, Cloudflare's Flexible SSL connects to origin port 80, but MoaV's CDN listener runs on port 2082.
Step 1: Go to Rules → Origin Rules
- In Cloudflare Dashboard, select your domain
- Navigate to Rules → Origin Rules
- Click Create rule
Step 2: Configure the Rule
| Field | Value |
|---|---|
| Rule name | CDN to port 2082 |
| When incoming requests match... | Hostname equals cdn.yourdomain.com |
| Then... | Destination Port → Rewrite to 2082 |
Step 3: Deploy
Click Deploy to activate the rule.
Verify it works:
# Should return HTTP 400 (sing-box responding, not Cloudflare 521)
# Use any path - the CDN WS path is auto-generated during bootstrap
curl -s -o /dev/null -w "%{http_code}" https://cdn.yourdomain.com/test
A 400 or 404 response means sing-box is receiving the request.
- 521 = Origin Rule is missing or misconfigured
- 525 = SSL mode is wrong — set Cloudflare SSL/TLS to Flexible (not Full/Strict), because MoaV's CDN port 2082 is plain HTTP
Important: Cloudflare SSL/TLS mode must be set to Flexible for CDN mode. MoaV's CDN inbound on port 2082 is plain HTTP (Cloudflare terminates TLS). If you need Full SSL for other subdomains, use a Configuration Rule to set Flexible for just
cdn.yourdomain.com.
See CDN Setup Guide for complete CDN configuration.
AWS CloudFront (Alternative CDN)¶
CloudFront can be used as an alternative to Cloudflare for CDN-fronted VLESS. The main advantage: no domain required — CloudFront gives you a *.cloudfront.net domain automatically. This is useful when you can't register a domain or want an additional CDN fallback.
How it works: Client connects to CloudFront (AWS CDN IPs) → CloudFront forwards to your server on port 2082 → sing-box handles the VLESS+WS connection. DPI only sees connections to AWS infrastructure.
Important: CloudFront Requires a Domain Name as Origin¶
CloudFront does not accept bare IP addresses as origins — you'll get InvalidArgument: The parameter origin name cannot be an IP address. If you already have a domain, use it. If not, use the free wildcard DNS service sslip.io:
For example, 139.59.22.221.sslip.io resolves to 139.59.22.221. This works with any IP and requires no registration. sslip.io is purely a DNS service — no traffic passes through it. Users never interact with it; only CloudFront uses it internally to reach your server.
Step 1: Create CloudFront Distribution¶
Option A: AWS Console (Web UI)¶
- Log into AWS Console
- Click Create Distribution
- Configure origin:
| Setting | Value |
|---|---|
| Origin domain | YOUR_SERVER_IP.sslip.io (or your domain) |
| Protocol | HTTP only |
| HTTP port | 2082 |
| HTTPS port | 443 |
Note: Type your origin domain (e.g.,
139.59.22.221.sslip.io) directly into the "Origin domain" field. Ignore the dropdown suggestions (S3 buckets, etc.) — CloudFront accepts any valid domain name.
- Configure behavior:
| Setting | Value |
|---|---|
| Viewer protocol policy | HTTPS only |
| Allowed HTTP methods | GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE |
| Cache policy | CachingDisabled |
| Origin request policy | AllViewer |
- Configure WebSocket support:
| Setting | Value |
|---|---|
| Response headers policy | None |
CloudFront supports WebSocket natively — no extra configuration needed.
- Click Create Distribution and wait for deployment (5-15 minutes)
- Note your distribution domain:
d1234abcd.cloudfront.net
Option B: AWS CLI¶
Install the CLI first: AWS CLI Installation Guide
Then authenticate:
# Option 1: SSO login (recommended if your org uses AWS IAM Identity Center)
aws sso login
# Option 2: Configure with access keys (from IAM → Users → Security credentials)
aws configure
Create the distribution (replace YOUR_SERVER_IP):
aws cloudfront create-distribution --distribution-config '{
"CallerReference": "moav-cdn-'$(date +%s)'",
"Comment": "MoaV CDN",
"Enabled": true,
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "moav-origin",
"DomainName": "YOUR_SERVER_IP.sslip.io",
"CustomOriginConfig": {
"HTTPPort": 2082,
"HTTPSPort": 443,
"OriginProtocolPolicy": "http-only"
}
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "moav-origin",
"ViewerProtocolPolicy": "https-only",
"AllowedMethods": {
"Quantity": 7,
"Items": ["GET","HEAD","OPTIONS","PUT","POST","PATCH","DELETE"],
"CachedMethods": {"Quantity": 2, "Items": ["GET","HEAD"]}
},
"CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
"OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3",
"Compress": false
},
"PriceClass": "PriceClass_200"
}'
Fix Existing Distribution (Missing Policies)¶
If you already created a distribution without the correct policies (common cause of bad "Sec-WebSocket-Key" header errors), fix it with:
# Download current config
aws cloudfront get-distribution-config --id YOUR_DISTRIBUTION_ID > /tmp/cf-config.json
# Add AllViewer origin request policy + CachingDisabled cache policy
jq '.DistributionConfig.DefaultCacheBehavior.OriginRequestPolicyId = "216adef6-5c7f-47e4-b989-5492eafa07d3" | .DistributionConfig.DefaultCacheBehavior.CachePolicyId = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" | .DistributionConfig' /tmp/cf-config.json > /tmp/cf-update.json
# Apply update (ETag is required for optimistic locking)
ETAG=$(jq -r '.ETag' /tmp/cf-config.json)
aws cloudfront update-distribution --id YOUR_DISTRIBUTION_ID --if-match "$ETAG" --distribution-config file:///tmp/cf-update.json
Wait 5-10 minutes for deployment. The key policies:
- AllViewer (216adef6-...) — forwards all client headers including WebSocket upgrade headers to your origin
- CachingDisabled (4135ea2d-...) — prevents caching, which breaks WebSocket connections
Example: For server IP
139.59.22.221, use"DomainName": "139.59.22.221.sslip.io"
The PriceClass controls which edge locations (regions) are used:
| PriceClass | Regions | Cost |
|---|---|---|
PriceClass_100 |
US, Canada, Europe | Cheapest |
PriceClass_200 |
+ Asia, Middle East, Africa | Mid-tier |
PriceClass_All |
All edge locations worldwide | Most expensive |
For users in Iran/Middle East, use
PriceClass_200orPriceClass_Allfor better latency — these include Middle East and Asia edge nodes.Note: You can't pick a specific datacenter/city. CloudFront is an anycast CDN — it automatically routes users to the nearest edge location within your selected price class.
Get your distribution domain:
Wait for deployment to complete (status changes from InProgress to Deployed):
aws cloudfront list-distributions \
--query 'DistributionList.Items[*].[Id,DomainName,Status]' --output table
Step 2: Configure MoaV¶
In your .env file:
# Use CloudFront instead of Cloudflare for CDN
CDN_SUBDOMAIN= # Leave empty (not using Cloudflare subdomain)
CDN_DOMAIN=d1234abcd.cloudfront.net
CDN_ADDRESS=d1234abcd.cloudfront.net
CDN_SNI=d1234abcd.cloudfront.net
CDN_TRANSPORT=ws # IMPORTANT: must be 'ws' for CloudFront (not 'httpupgrade')
Important: CloudFront requires
CDN_TRANSPORT=ws(standard WebSocket). The defaulthttpupgradeis a sing-box-specific protocol that works with Cloudflare but fails on CloudFront withbad "Sec-WebSocket-Key" headererrors. If you switch from Cloudflare to CloudFront, change this setting and re-bootstrap.
Then re-bootstrap to regenerate configs:
Step 3: Verify¶
# Should return 400 (sing-box responding)
curl -s -o /dev/null -w "%{http_code}" https://d1234abcd.cloudfront.net/test
CloudFront vs Cloudflare¶
| Feature | Cloudflare | CloudFront |
|---|---|---|
| Cost | Free tier | ~$0.085/GB (1TB free/month for 12 months) |
| Domain required | Yes | No (get *.cloudfront.net) |
| Setup complexity | DNS + Origin Rule | AWS Console distribution |
| WebSocket support | Yes | Yes |
| Origin port config | Needs Origin Rule to rewrite to 2082 | Direct port configuration |
| SNI flexibility | Can use root domain for stealth | Must use *.cloudfront.net or your CNAME |
| Domain fronting | Partially supported | Blocked since 2018 |
| Global edge network | Yes (larger) | Yes |
SNI note: CloudFront validates that the TLS SNI matches your distribution domain or a configured CNAME. You cannot use an arbitrary domain (like
google.com) as SNI — AWS blocked domain fronting in 2018. For Reality-based protocols (XHTTP, VLESS+Reality), SNI camouflage works differently and does not rely on the CDN.
Using Both Cloudflare and CloudFront¶
You can run both CDN providers simultaneously for redundancy. Users get two CDN share links — if one CDN's IPs get blocked, the other likely still works:
- Set up Cloudflare CDN as described above (requires domain)
- Set up CloudFront as a second distribution pointing to the same server
- Share both links with users
To generate links for both, you'd need to run bootstrap with Cloudflare settings first, then manually create CloudFront share links using the same UUID and WS path.
CloudFront CLI Management¶
# List all distributions
aws cloudfront list-distributions \
--query 'DistributionList.Items[*].[Id,DomainName,Status]' --output table
# Check which edge location a user is hitting
curl -sI https://d1234abcd.cloudfront.net/ | grep x-amz-cf-pop
# Example output: x-amz-cf-pop: FRA56-P4 (Frankfurt)
# Disable a distribution (must disable before deleting)
# First get the ETag:
ETAG=$(aws cloudfront get-distribution-config --id DIST_ID \
--query 'ETag' --output text)
# Then disable:
aws cloudfront get-distribution-config --id DIST_ID \
--query 'DistributionConfig' --output json \
| jq '.Enabled = false' \
| aws cloudfront update-distribution --id DIST_ID \
--if-match "$ETAG" --distribution-config file:///dev/stdin
# Delete (only after status is "Deployed" and distribution is disabled)
aws cloudfront delete-distribution --id DIST_ID --if-match "$ETAG"
Namecheap¶
- Log into Namecheap
- Domain List → Manage → Advanced DNS
- Add records:
| Type | Host | Value | TTL |
|---|---|---|---|
| A Record | @ | YOUR_IP | Automatic |
| A Record | dns | YOUR_IP | Automatic |
| NS Record | t | dns.yourdomain.com. | Automatic |
| NS Record | s | dns.yourdomain.com. | Automatic |
Note: NS value may need trailing dot. The dns, t, and s records are only needed if using DNS tunnels.
Google Domains / Squarespace¶
- Go to DNS settings
- Add custom records:
| Host name | Type | TTL | Data |
|---|---|---|---|
| (blank) | A | 300 | YOUR_IP |
| dns | A | 300 | YOUR_IP |
| t | NS | 300 | dns.yourdomain.com |
| s | NS | 300 | dns.yourdomain.com |
Hetzner DNS¶
- Go to DNS Console
- Select your zone
- Add records:
Home Servers & Raspberry Pi¶
MoaV runs on Raspberry Pi 4+ (2GB+ RAM) and any ARM64/x64 Linux machine. Home servers typically have dynamic IPs and sit behind a router, so you need port forwarding and (if using a domain) Dynamic DNS.
Port Forwarding¶
Configure your router to forward the ports you need to your MoaV server's local IP.
Domainless mode (minimum):
| Port | Protocol | Service |
|---|---|---|
| 443/tcp | TCP | Reality (VLESS) |
| 51820/udp | UDP | WireGuard |
| 8080/tcp | TCP | wstunnel (WireGuard over WebSocket) |
| 51821/udp | UDP | AmneziaWG |
| 993/tcp | TCP | Telegram MTProxy |
| 9443/tcp | TCP | Admin Dashboard |
With domain (add these):
| Port | Protocol | Service |
|---|---|---|
| 80/tcp | TCP | Let's Encrypt verification (only during certificate setup/renewal) |
| 443/udp | UDP | Hysteria2 |
| 8443/tcp | TCP | Trojan |
| 4443/tcp | TCP | TrustTunnel (HTTP/2) |
| 4443/udp | UDP | TrustTunnel (HTTP/3 / QUIC) |
| 53/udp | UDP | DNS tunnels (dnstt / Slipstream / XDNS) |
Optional:
| Port | Protocol | Service |
|---|---|---|
| 9444/tcp | TCP | Grafana monitoring dashboard |
Only forward ports for protocols you actually enable. Check your
.envfile forENABLE_*toggles.
Before You Start¶
-
Check for CGNAT: Some ISPs use Carrier-Grade NAT which prevents incoming connections entirely. Test by comparing your router's WAN IP with
curl ipinfo.io/ip. If they differ, contact your ISP for a public IP or use a VPS instead. -
Static local IP: Assign a static IP to your MoaV server in your router's DHCP settings so port forwarding rules don't break when the local IP changes.
Dynamic DNS (DDNS)¶
If you're using a domain with a home server, your ISP likely assigns a dynamic public IP that changes periodically. Dynamic DNS services automatically update your domain to point to your current IP.
Domainless mode does not need DDNS. Users connect via your public IP directly. You can find your current public IP with
curl ifconfig.meand share it manually. If your IP changes, update the configs you shared.
DuckDNS (Free)¶
DuckDNS is a free DDNS service that provides subdomains like yourname.duckdns.org. Let's Encrypt works with DuckDNS domains.
Step 1: Create Account¶
- Go to duckdns.org
- Sign in with Google, GitHub, Twitter, or Reddit
- Create a subdomain (e.g.,
myvpn→myvpn.duckdns.org) - Copy your token from the dashboard
Step 2: Install Update Script¶
On your MoaV server (Raspberry Pi or home server):
# Create update script
mkdir -p /opt/duckdns
cat > /opt/duckdns/duck.sh << 'EOF'
#!/bin/bash
DOMAIN="YOUR_SUBDOMAIN" # e.g., myvpn (without .duckdns.org)
TOKEN="YOUR_TOKEN"
curl -s "https://www.duckdns.org/update?domains=${DOMAIN}&token=${TOKEN}&ip=" | logger -t duckdns
EOF
# Replace with your values
nano /opt/duckdns/duck.sh
# Make executable
chmod +x /opt/duckdns/duck.sh
# Test it
/opt/duckdns/duck.sh
Step 3: Schedule Automatic Updates¶
# Add to crontab (runs every 5 minutes)
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/duckdns/duck.sh") | crontab -
Step 4: Configure MoaV¶
In your .env file:
Then run bootstrap as normal.
Note: DuckDNS subdomains don't support NS delegation, so DNS tunnels (dnstt, Slipstream, XDNS) won't work with DuckDNS. All other domain-based protocols work fine.
Cloudflare DDNS (Own Domain)¶
If you have your own domain on Cloudflare, you can use the Cloudflare API to update DNS records automatically. This supports all features including DNS tunnels.
Step 1: Get API Token¶
- Go to Cloudflare Dashboard → My Profile → API Tokens
- Create a token with Zone:DNS:Edit permission for your domain
- Copy the token
Step 2: Get Zone ID¶
- Go to your domain in Cloudflare
- Scroll down on the Overview page
- Copy the Zone ID from the right sidebar
Step 3: Install Update Script¶
mkdir -p /opt/cloudflare-ddns
cat > /opt/cloudflare-ddns/update.sh << 'EOF'
#!/bin/bash
# Configuration
CF_API_TOKEN="YOUR_API_TOKEN"
CF_ZONE_ID="YOUR_ZONE_ID"
DOMAIN="yourdomain.com"
RECORD_NAME="@" # Use "@" for root domain or "subdomain" for subdomain
# Get current public IP
CURRENT_IP=$(curl -s https://api.ipify.org)
# Get current DNS record
RECORD_DATA=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?type=A&name=${DOMAIN}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
RECORD_ID=$(echo "$RECORD_DATA" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
RECORD_IP=$(echo "$RECORD_DATA" | grep -o '"content":"[^"]*"' | head -1 | cut -d'"' -f4)
# Update if IP changed
if [ "$CURRENT_IP" != "$RECORD_IP" ]; then
echo "IP changed from $RECORD_IP to $CURRENT_IP, updating..."
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${DOMAIN}\",\"content\":\"${CURRENT_IP}\",\"ttl\":300,\"proxied\":false}" | logger -t cloudflare-ddns
else
echo "IP unchanged ($CURRENT_IP)"
fi
EOF
# Edit with your values
nano /opt/cloudflare-ddns/update.sh
chmod +x /opt/cloudflare-ddns/update.sh
Step 4: Schedule Updates¶
# Run every 5 minutes
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/cloudflare-ddns/update.sh") | crontab -
Step 5: Configure MoaV¶
In your .env:
After DDNS Setup¶
- Wait for propagation: After the first update, wait 5-10 minutes
- Verify:
dig +short yourdomain.comshould show your home IP - Run MoaV setup:
moavto start the interactive setup - Test from outside: Use mobile data (not home WiFi) to test connectivity
Home Server Tips¶
- UPS recommended: Protect against power outages, especially for Raspberry Pi
- Monitor uptime: Use a free service like UptimeRobot to alert you if your server goes down
- Backup regularly:
moav exportto backup your configuration - Temperature: Ensure adequate cooling for Raspberry Pi under sustained VPN load
- SD card: Use a high-endurance microSD card or boot from USB/SSD for reliability
Verification¶
After configuring DNS, wait for propagation (usually 5-30 minutes, up to 48 hours).
Verify with MoaV¶
Verify A Record¶
dig +short yourdomain.com
# Should return: YOUR_SERVER_IP
dig +short dns.yourdomain.com
# Should return: YOUR_SERVER_IP
Verify NS Delegation¶
dig NS t.yourdomain.com
# Should show: dns.yourdomain.com in AUTHORITY SECTION
# Test that queries reach your server
dig @YOUR_SERVER_IP test.t.yourdomain.com
# Should get a response (after dnstt is running)
Online Tools¶
- https://dnschecker.org - Check propagation worldwide
- https://mxtoolbox.com/DNSLookup.aspx - Detailed DNS lookup
Common Issues¶
"DNS not propagated yet"¶
Wait longer (up to 48 hours in rare cases). Check with multiple DNS servers:
"NS record not working"¶
- Ensure the A record for
dns.yourdomain.comexists - Some registrars require a trailing dot:
dns.yourdomain.com. - NS delegation can take longer to propagate
"Certificate acquisition failed"¶
- Verify A record is correct:
dig yourdomain.com - Ensure port 80 is open (temporarily, for ACME HTTP-01)
- Check that no other service is using port 80
- Not applicable in domainless mode (no certificates needed)
"Can't connect from outside my home network"¶
- Verify port forwarding is configured on your router
- Check for CGNAT:
curl ifconfig.meshould match your router's WAN IP - Ensure your ISP doesn't block the ports you need
- Test from mobile data, not your home WiFi
Domain Acquisition Tips¶
For users in censored regions:
- Use privacy protection - Hide your personal info in WHOIS
- Pay with crypto if possible - For anonymity
- Choose a neutral TLD -
.com,.net,.orgare less suspicious than country-specific TLDs - Avoid "VPN" or "proxy" in the domain name - Keep it generic
- Consider multiple domains - Have backups ready if one gets blocked
Domain Naming Strategy¶
Your domain name is the first thing DPI systems see in the TLS SNI. A good domain blends with legitimate traffic:
Good examples:
- Names that look like business infrastructure: cloudops-services.com, cdn-platform.net
- Names that look like SaaS products: dataflow-sync.com, metrics-hub.net
- Generic tech names: stackbuilder.io, nodebridge.net
Bad examples:
- Anything with "vpn", "proxy", "tunnel", "free", "bypass" in the name
- Random strings: xk4m2p.com (suspicious to automated systems)
- Known circumvention patterns: v2ray-server.com
Subdomain naming also matters. MoaV's CDN subdomain defaults to cdn — consider changing it to something like assets, static, api, or app in your .env:
Recommended Registrars¶
- Namecheap - Good privacy, accepts crypto
- Porkbun - Cheap, good privacy
- Njalla - Maximum privacy (they own the domain for you)