This document describes how to integrate HTTPDNS with the iOS 14 native encrypted DNS solution.
Overview
DNS resolution is the first network hop an app makes. Traditional Local DNS sends queries over unencrypted UDP, leaving them exposed to hijacking or interception. iOS 14 natively supports two standard encrypted DNS protocols:
DNS over HTTPS (DoH): routes queries inside standard HTTPS traffic on port 443, making them indistinguishable from web requests and harder to block or monitor.
DNS over TLS (DoT): encrypts queries with TLS on a dedicated port (853), which is faster to implement but easier for network intermediaries to identify and filter.
For most apps, DoH is the simpler choice — it requires no special firewall rules and blends into existing HTTPS traffic.
HTTPDNS provides an SDK-based solution, but it introduces integration overhead: you must handle direct IP connections in 302 redirect scenarios and work around Server Name Indication (SNI) issues on iOS. The iOS 14 native encrypted DNS feature eliminates these concerns by letting the system handle encrypted resolution natively, with HTTPDNS as the resolver.
The iOS 14 native encrypted DNS solution is supported only on physical devices running iOS 14 or later.
Implement a fallback mechanism to Local DNS to prevent resolution failures if the DoH or DoT service is unavailable.
Unlike the SDK integration solution, the native solution does not support automatic service failover or provide a Service-Level Agreement (SLA). If you adopt the native solution in production, implement service exception monitoring and automatic failover to the device's Local DNS.
Integrate HTTPDNS with the iOS 14 native encrypted DNS solution
iOS 14 supports two scopes of encrypted DNS configuration. Choose the scope that fits your use case:
|
Method |
Scope |
API |
Language |
|
App-level |
Encrypted DNS for all connections within a single app |
|
Objective-C |
|
System-wide |
Encrypted DNS for all apps on the device |
|
Swift |
-
Enable encrypted DNS for a single app
To use encrypted DNS within your app only — without affecting other apps on the device — use nw_privacy_context_t from NetworkExtension. Every DNS resolution initiated within the app uses this configuration.
A reference demo is available: demo that enables DoH for all connections of a single app.
All four app-level examples use nw_privacy_context_require_encrypted_name_resolution on NW_DEFAULT_PRIVACY_CONTEXT to activate encrypted resolution. The HTTPDNS server addresses, protocol, and fallback servers are the only parameters that differ between DoH and DoT.
App-level DoH (Objective-C)
#import <NetworkExtension/NetworkExtension.h>
if (@available(iOS 14.0, *)){
nw_privacy_context_t defaultPrivacyContext = NW_DEFAULT_PRIVACY_CONTEXT;
nw_endpoint_t dohResolverEndpoint = nw_endpoint_create_url("https://*****-************.alidns.com/dns-query");//DoH encrypted address
nw_endpoint_t v4ResolverEndpoint1 = nw_endpoint_create_host("223.5.5.5", "443");
nw_endpoint_t v4ResolverEndpoint2 = nw_endpoint_create_host("223.6.6.6", "443");
nw_endpoint_t v6ResolverEndpoint1 = nw_endpoint_create_host("2400:3200::1", "443");
nw_endpoint_t v6ResolverEndpoint2 = nw_endpoint_create_host("2400:3200:baba::1", "443");
nw_resolver_config_t fallbackResolvers = nw_resolver_config_create_https(dohResolverEndpoint);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint2);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint2);
nw_privacy_context_require_encrypted_name_resolution(defaultPrivacyContext, true, fallbackResolvers);
}
App-level DoT (Objective-C)
#import <NetworkExtension/NetworkExtension.h>
if (@available(iOS 14.0, *)){
nw_privacy_context_t defaultPrivacyContext = NW_DEFAULT_PRIVACY_CONTEXT;
nw_endpoint_t dotResolverEndpoint = nw_endpoint_create_host("******-************.alidns.com", "853");//DoT encrypted address
nw_endpoint_t v4ResolverEndpoint1 = nw_endpoint_create_host("223.5.5.5", "853");
nw_endpoint_t v4ResolverEndpoint2 = nw_endpoint_create_host("223.6.6.6", "853");
nw_endpoint_t v6ResolverEndpoint1 = nw_endpoint_create_host("2400:3200::1", "853");
nw_endpoint_t v6ResolverEndpoint2 = nw_endpoint_create_host("2400:3200:baba::1", "853");
nw_resolver_config_t fallbackResolvers = nw_resolver_config_create_tls(dotResolverEndpoint);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v4ResolverEndpoint2);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint1);
nw_resolver_config_add_server_address(fallbackResolvers, v6ResolverEndpoint2);
nw_privacy_context_require_encrypted_name_resolution(defaultPrivacyContext, true, fallbackResolvers);
}
Fall back to Local DNS
Because the native solution provides no automatic failover, you must detect resolution failures and revert to Local DNS manually. Use URLSessionDelegate to read NSURLSessionTaskTransactionMetrics after each request. If domainResolutionProtocol returns Unknown, the encrypted resolver is unreachable — disable it immediately.
The protocol values map as follows:
|
Value |
Protocol |
|
|
Unknown |
|
|
UDP |
|
|
TCP |
|
|
TLS |
|
|
HTTPS |
#pragma mark URLSession Delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
if ([metrics.transactionMetrics count] <= 0) return;
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
if (@available(iOS 14.0, *)) {
NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
NSLog(@"%@",[NSString stringWithFormat:@"DNS type is %ld", (long)dnsProtocol]);
NSLog(@"%@",[NSString stringWithFormat:@"0:Unknown,1:UDP,2:TCP,3:TLS,4:HTTPS"]);
if (dnsProtocol == NSURLSessionTaskMetricsDomainResolutionProtocolUnknown) {
NSLog(@"%@",[NSString stringWithFormat:@"DNS source unknown, disabling DoH"]);
nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
}
}
}
}];
}
-
Enable encrypted DNS system-wide
To apply encrypted DNS to all apps on the device, create a NetworkExtension app that uses the NEDNSSettingsManager API. The system manages the DNS configuration — you do not need to implement a Network Extension provider or plugin. Select DNS Settings under Network Extensions in your Xcode project.
A DNS configuration consists of three parts: the HTTPDNS server addresses, the protocol (DoH or DoT), and a set of NEOnDemandRule network rules that control when the configuration is active.
System-wide DoH (Swift)
import NetworkExtension
NEDNSSettingsManager.shared().loadFromPreferences { loadError in
if let loadError = loadError {
// ...handle error...
return
}
let dohSettings = NEDNSOverHTTPSSettings(servers: ["223.5.5.5","223.6.6.6","2400:3200:baba::1","2400:3200::1"])
dohSettings.serverURL = URL(string: "https://*****-************.alidns.com/dns-query")//DoH encrypted address
NEDNSSettingsManager.shared().dnsSettings = dohSettings
NEDNSSettingsManager.shared().saveToPreferences { saveError in
if let saveError = saveError {
// ...handle error...
return
}
}
}
System-wide DoT (Swift)
import NetworkExtension
NEDNSSettingsManager.shared().loadFromPreferences { loadError in
if let loadError = loadError {
// ...handle error...
return
}
let dotSettings = NEDNSOverTLSSettings(servers: ["223.5.5.5","223.6.6.6","2400:3200:baba::1","2400:3200::1"])
dotSettings.serverName = "******-************.alidns.com"//DoT encrypted address
NEDNSSettingsManager.shared().dnsSettings = dotSettings
NEDNSSettingsManager.shared().saveToPreferences { saveError in
if let saveError = saveError {
// ...handle error...
return
}
}
}
Configure network rules
Network rules (NEOnDemandRule) control which networks and SSIDs activate the DNS configuration. The following example sets three rules:
On the Wi-Fi network
MyWorkWiFi, encrypted DNS is active — but the private domainenterprise.exampleis resolved locally (never routes through the encrypted resolver).On cellular networks, encrypted DNS is disabled.
-
On all other networks, encrypted DNS is enabled by default.
let workWiFi = NEOnDemandRuleEvaluateConnection() workWiFi.interfaceTypeMatch = .wiFi workWiFi.ssidMatch = ["MyWorkWiFi"] workWiFi.connectionRules = [NEEvaluateConnectionRule(matchDomains: ["enterprise.example"], andAction: .neverConnect)] let disableOnCell = NEOnDemandRuleDisconnect() disableOnCell.interfaceTypeMatch = .cellular let enableByDefault = NEOnDemandRuleConnect() NEDNSSettingsManager.shared().onDemandRules = [ workWiFi, disableOnCell, enableByDefault ]
Activate the configuration
After running the NetworkExtension app, the DNS configuration is installed on the device but not yet active. To enable it, go to Settings > General > VPN & Network > DNS and turn on the configuration.

