Start your 3-day free trial
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.


sing-box DNS migration requires preserving resolution and routing behavior while replacing the server schema. This guide maps one UDP bootstrap and one HTTPS upstream from a sing-box 1.13.21 historical input to 1.14.3, on Ubuntu 24.04 LTS amd64. Legacy DNS servers were removed in 1.14.0. The old version explains the input; it is not a recommendation to keep running it. No complete end-to-end author test was performed.[1][2][6][7]
Key Takeaways
- Keep tags, bootstrap dependencies, and routing intent, not just valid JSON.
- A hostname-based HTTPS upstream needs an independent domain resolver.
- Typed DNS defaults to direct dialing; an explicit detour changes which dial fields apply.
- Restore the binary, configuration, and service settings as one matching set.
Start with the self-hosting maintenance checklist and identify who produces the effective file. If a GUI, panel, subscription, or generator owns it, use its supported migration path and check how profile updates reach the client. An overwritten local edit is not a durable migration.
The core and client responsibility guide helps distinguish schema ownership from an interface setting. Changing DNS servers does not prove that TUN or system-proxy routing carries the traffic you intended. For unrelated startup failures, use the device and app diagnosis sequence.
Use a private working directory, an available loopback port 1053, dig from Ubuntu's dnsutils package, and a separately obtained official 1.14.3 executable at /opt/sing-box-1.14.3/sing-box. Check the release artifact's platform and official integrity information before use. Do not overwrite the running binary or download and execute a remote installation script. Preserve the currently deployed executable privately if it is needed to interpret the historical file.
Record the actual executable path and version, effective configuration, include directories, unit and drop-ins, service identity, ownership, permissions, and enablement. Keep backups outside the active include tree so the service cannot load them accidentally. Verify an independent recovery connection before touching a running service.
The following input deliberately contains no rules, cache tuning, FakeIP, query filtering, or strategy override. Save it as dns-old.json in your private exercise directory. It is an explanatory fixture, not a command to replace a working installation.
{
"dns": {
"servers": [
{
"tag": "bootstrap",
"address": "1.1.1.1",
"detour": "direct"
},
{
"tag": "secure-dns",
"address": "https://dns.google/dns-query",
"address_resolver": "bootstrap",
"detour": "direct"
}
],
"final": "secure-dns"
},
"inbounds": [
{
"type": "direct",
"tag": "dns-test",
"listen": "127.0.0.1",
"listen_port": 1053
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
{
"inbound": [
"dns-test"
],
"action": "hijack-dns"
}
],
"final": "direct"
}
}
bootstrap resolves the hostname of secure-dns; the latter handles queries selected by dns.final. The direct inbound accepts TCP and UDP on loopback when network is omitted. The inbound-specific route action sends those requests to DNS handling. It does not become a public resolver or capture system-wide DNS.[5][9]
Before mapping a real file, list every server tag and all references from DNS rules, final, route options, and outbound dialers. Record which upstream uses a numeric address, which uses a hostname, and which proxy carries it. A name can exist but still refer to the wrong role. If your file contains additional strategies or filters, stop this minimal recipe and migrate those semantics separately against the official guide.[1]
Use the table to explain each change. Do not run a global text replacement: the same word can belong to different object types or control a different decision.
| Historical input | Typed target | What must remain true |
|---|---|---|
address: 1.1.1.1 | type: udp, numeric server, port 53 | Bootstrap can be reached without another hostname lookup |
HTTPS URL in address | type: https, server, port 443, path | Hostname, URL path, and verified TLS identity match the selected service |
address_resolver: bootstrap | domain_resolver: bootstrap | The hostname of the upstream resolves without consulting itself |
Server tag and dns.final | Retained strings | All references resolve to the intended server |
Explicit detour: direct in this input | Omitted in this target | Typed servers use their documented direct-dial default |
| Strategy or query policy elsewhere | Case-specific mapping | Answer selection and filtering semantics require separate review |
Typed UDP and HTTPS servers use direct dialing by default, whereas legacy servers could inherit a default outbound. This example's old servers explicitly select direct, so omitting detour preserves that intent. If the original uses a proxy detour, do not remove it merely to resemble this sample: trace its outbound and bootstrap path. An enabled detour delegates dialing and causes other dial fields to be ignored; putting domain_resolver beside it does not automatically configure the chosen outbound's resolver.[3][4][8]
Save the mapped file as dns-new.json:
{
"dns": {
"servers": [
{
"type": "udp",
"tag": "bootstrap",
"server": "1.1.1.1",
"server_port": 53
},
{
"type": "https",
"tag": "secure-dns",
"server": "dns.google",
"server_port": 443,
"path": "/dns-query",
"domain_resolver": "bootstrap",
"tls": {
"enabled": true,
"server_name": "dns.google",
"insecure": false
}
}
],
"final": "secure-dns"
},
"inbounds": [
{
"type": "direct",
"tag": "dns-test",
"listen": "127.0.0.1",
"listen_port": 1053
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
{
"inbound": [
"dns-test"
],
"action": "hijack-dns"
}
],
"final": "direct"
}
}
This hostname-based HTTPS server explicitly names its independent UDP bootstrap. Keep TLS verification enabled. TLS protects the HTTPS leg; the UDP bootstrap itself is not encrypted. Choosing these public upstreams is an example of dependency separation, not a claim about privacy, availability, latency, or regional reachability.[4]
Check the old fixture only with a retained matching executable if available. An old-version deprecation refusal is not a reason to disable a safety gate or install that version on a production host. The essential target checks use the separately staged 1.14.3 executable:
/PRIVATE/BACKUP/sing-box-1.13.21 check -c dns-old.json
/opt/sing-box-1.14.3/sing-box version
/opt/sing-box-1.14.3/sing-box check -c dns-new.json
/opt/sing-box-1.14.3/sing-box run -c dns-new.json
Run the foreground target as an ordinary user; port 1053 needs no privileged-port capability. Keep the exercise files private with mode 600. If loopback 1053 is occupied, stop and choose another unused high port consistently rather than killing the owner. A check that exits successfully establishes schema acceptance only. The running process and real queries establish different evidence.
From another terminal, query its explicit address and port:
dig @127.0.0.1 -p 1053 example.com A
dig @127.0.0.1 -p 1053 example.com AAAA
dig @127.0.0.1 -p 1053 example.com A +tcp
Inspect the DNS status, requested record type, answer, and reported server. A successful answer need not contain an IPv6 record when the zone has none. Results from a browser or an unqualified dig command could use another resolver and do not validate this process. Record your observations privately; the commands above contain no fabricated output.
Create private copies, change one condition, Ctrl-C only the owned foreground process, and restart the selected copy. Restore the known-good file between checks. Do not change a shared firewall or system DNS for these tests.
| Reader check | Expected observation | Failure handling |
|---|---|---|
| Valid target, UDP and TCP queries | The process answers through the selected DNS path | Inspect binding, upstream access, TLS, and logs separately |
| Point HTTPS at an unused local port in a test copy | A real query fails even if the schema is accepted | Restore the endpoint; do not weaken TLS or add hidden fallback |
Replace domain_resolver with a nonexistent tag | Configuration check or startup rejects the reference | Correct the reference before running queries |
Make secure-dns resolve its own hostname through itself | Rejection or resolution failure, not a valid bootstrap | Restore the numeric independent bootstrap; do not rely on an exact error string |
| Run the old legacy fixture with the target binary | Legacy server schema is refused | Use the mapped target, not a deprecation bypass |
| Restart the unchanged valid target | New queries work again | Compare executable, file, and permissions with the private baseline |
A failure caused by an upstream outage can look like a migration failure. Separate schema rejection, socket binding, name bootstrap, TLS, and answer policy. These expected observations are not reported author results. Do not treat a timeout as proof of a specific cause.
Only after isolated checks, use the existing owner's supported deployment procedure to point the service at the target executable and validated file. Recheck its identity, certificate access if relevant, unit command, and include paths. Check the effective configuration as that identity, restart only the owned unit in a maintenance window, inspect bounded logs, and repeat explicit queries through the actual service entry point.
To recover, stop the changed service, restore the saved executable or exact package, configuration and includes, ownership, unit and drop-ins, and previous enablement. If unit files changed, run sudo systemctl daemon-reload. Check with the restored matching binary, start according to the prior state, inspect status and logs, and repeat the original baseline queries. Restoring only JSON while leaving an incompatible binary is not recovery.
A rollback to a historical version is temporary incident containment, subject to its security support and advisories. Do not leave legacy production indefinitely; schedule a supported migration. For the isolated exercise, Ctrl-C the owned process and verify port 1053 is released. No client system DNS was changed, so there is no router-wide DNS switch to undo.
No. Their removal boundary is 1.14.0; use typed servers rather than a legacy-schema bypass.[2]
address_resolver always replaced with domain_resolver?Map the field in its specific server and dialing context. A global replacement cannot preserve proxy ownership or policy automatically.
Rules and final selections refer to them. Renaming a tag requires updating every intended reference, while retaining a tag still requires checking its role.
Its hostname must resolve before the HTTPS connection exists. Resolving that name through the same unavailable connection creates a dependency loop.
detour: direct?No. Typed servers already default to direct dialing. An enabled detour changes which dial fields apply; review the actual outbound's resolver separately.[3][4]
sing-box check prove DNS works?No. It checks configuration. Real queries, answer policy, upstream connectivity, and restart behavior need their own observations.
Migrate its source or supported template with the maintainer. A manual edit to generated output does not establish lasting ownership.
Disclaimer: Use only authorized hosts and networks, and follow applicable law and policy. These are documentation-based steps and reader verification criteria, not a deployment record or a performance guarantee.
Sources:
Sources checked 11 October 2026.
Related Articles:
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.