All Products
Search
Document Center

:Use DoH in iOS clients

Last Updated:Jun 03, 2026

Integrate HTTPDNS in iOS apps through DoH at the application level or system level.

Background

The common approach is to import the HTTPDNS SDK, then handle HTTPS certificate verification and SNI extension to use HTTPDNS resolution in your app. Use HTTPDNS in native iOS scenarios.

iOS 14+ introduced secure DNS, enabling HTTPDNS integration through DoH. Related WWDC sessions:

Important

Secure DNS is natively supported by iOS and takes effect at the app or device network level without code-level changes to individual requests. However, it has significant limitations:

  • It applies to the entire app or device. All DNS resolutions in that scope go through HTTPDNS, including third-party SDK requests, with no fine-grained control.

  • Device-level configuration requires special user permissions, typically available only to network utility apps.

Prerequisites

DoH is enabled and the DoH endpoint is obtained. Configure the DoH service.

Note
  • If DoH is not enabled, resolution requests fail with a 400 error code.

  • If Domain Resolution Scope is set to Domains in the domain list, the HTTPDNS server returns 200 with no resolution result for unlisted domains.

  • If Domain Resolution Scope is set to All domains, the HTTPDNS server returns 200 with no resolution results for blacklisted domains.

Configure application-level DoH

The iOS 14+ privacyContext API in network.framework lets you configure DoH per app and control DNS resolution throughout the app lifecycle.

Sample code

Create a DataTaskManager to manage NSURLSession.

@interface DataTaskManager : NSObject <NSURLSessionTaskDelegate>
#import "DataTaskManager.h"
@import Network;
@import Foundation;

- (instancetype)init {
    self = [super init];
    if (self) {
        _networkQueue = dispatch_queue_create("com.taskmanager.queue", DISPATCH_QUEUE_SERIAL);
        [self setupDoHConfiguration];
    }
    return self;
}
- (void)setupDoHConfiguration {
    dispatch_async(self.networkQueue, ^{
        NSLog(@"Setting up DoH configuration...");
        
        // Create URL endpoint for HTTPDNS DoH
        const char *dohServerURL = "https://1xxxx3.aliyunhttpdns.com/dns-query";
        nw_endpoint_t urlEndpoint = nw_endpoint_create_url(dohServerURL);
        NSLog(@"Using DoH server: %s", dohServerURL);
        
        nw_resolver_config_t resolverConfig = nw_resolver_config_create_https(urlEndpoint);
        nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, true, resolverConfig);
        NSLog(@"DoH configuration applied to privacy context");
        
    });
}

Complete the initialization in ViewController.viewDidLoad.

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    ......
    self.dataTaskManager = [[DataTaskManager alloc] init];
    ......
}
Important

After DoH is configured, all URLSession-based requests in the app, including those from third-party libraries, use DoH for DNS resolution.

Instrument resolution performance

Use NSURLSessionTaskMetrics to verify DoH resolution is active and monitor DNS resolution time.

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    NSLog(@"\n=== Collecting metrics for request to: %@ ===\n", task.originalRequest.URL);
    
    task.taskDescription = [NSString stringWithFormat:@"%.2f,%@", 
                          metrics.taskInterval.duration,
                          task.originalRequest.URL.absoluteString];
    
    if ([metrics.transactionMetrics count] > 0) {   
        [metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
            NSString *fetchTypeStr = @"Unknown";
            switch (obj.resourceFetchType) {
                case NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad:
                    fetchTypeStr = @"Network Load";
                    break;
                case NSURLSessionTaskMetricsResourceFetchTypeServerPush:
                    fetchTypeStr = @"Server Push";
                    break;
                case NSURLSessionTaskMetricsResourceFetchTypeLocalCache:
                    fetchTypeStr = @"Local Cache";
                    break;
            }
            NSLog(@"Fetch Type: %@", fetchTypeStr);
            
            if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
                NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
                NSString *dnsProtocolStr = @"Unknown (0)";
                BOOL isDoH = NO;
                
                switch (dnsProtocol) {
                    case NSURLSessionTaskMetricsDomainResolutionProtocolUDP:
                        dnsProtocolStr = @"UDP (1)";
                        break;
                    case NSURLSessionTaskMetricsDomainResolutionProtocolTCP:
                        dnsProtocolStr = @"TCP (2)";
                        break;
                    case NSURLSessionTaskMetricsDomainResolutionProtocolTLS:
                        dnsProtocolStr = @"TLS (3)";
                        break;
                    case NSURLSessionTaskMetricsDomainResolutionProtocolHTTPS:
                        dnsProtocolStr = @"HTTPS/DoH (4)";
                        isDoH = YES;
                        break;
                }
                
                NSLog(@"DNS Protocol: %@", dnsProtocolStr);
                
                #if TARGET_OS_SIMULATOR
                    NSLog(@"Running in simulator - DNS protocol detection not supported");
                #else
                    if (!isDoH) {
                        NSLog(@"DoH not detected");
                    }
                #endif
                
                // Get DNS resolution performance data
                if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
                    int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
                    NSLog(@"DNS Lookup Details:");
                    NSLog(@"  Start: %@", obj.domainLookupStartDate);
                    NSLog(@"  End: %@", obj.domainLookupEndDate);
                    NSLog(@"  Duration: %d ms", dnsLookupTime);
                } else {
                    NSLog(@"No DNS lookup performed (might be cached)");
                }
                
                // Get network request performance data
                if (obj.connectStartDate && obj.connectEndDate) {
                    NSTimeInterval connectionTime = [obj.connectEndDate timeIntervalSinceDate:obj.connectStartDate];
                    NSLog(@"Connection Time: %.3f seconds", connectionTime);
                }
            }
        }];
    } else {
        NSLog(@"No transaction metrics available");
    }
}

Fallback mechanism

Important
  1. Falling back affects all subsequent URLSession-based requests in the app.

  2. DoH works on real iOS 14+ devices and in simulators. However, in simulators, NSURLSessionTaskMetricsDomainResolutionProtocol reported by NSURLSessionTaskMetrics is always 0 (Unknown).

  1. Configure timeout settings to detect DNS resolution failures promptly:

// Set connection timeout
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.waitsForConnectivity = NO;
config.timeoutIntervalForRequest = 5;
config.timeoutIntervalForResource = 10;
  1. Use the instrumentation chain to implement a fallback strategy. The following example falls back to local DNS when a DNS exception is detected:

  ......
            
    if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
        NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
        NSString *dnsProtocolStr = @"Unknown (0)";
        BOOL isDoH = NO;
        
        switch (dnsProtocol) {
            case NSURLSessionTaskMetricsDomainResolutionProtocolUDP:
                dnsProtocolStr = @"UDP (1)";
                break;
            case NSURLSessionTaskMetricsDomainResolutionProtocolTCP:
                dnsProtocolStr = @"TCP (2)";
                break;
            case NSURLSessionTaskMetricsDomainResolutionProtocolTLS:
                dnsProtocolStr = @"TLS (3)";
                break;
            case NSURLSessionTaskMetricsDomainResolutionProtocolHTTPS:
                dnsProtocolStr = @"HTTPS/DoH (4)";
                isDoH = YES;
                break;
        }
        
        NSLog(@"DNS Protocol: %@", dnsProtocolStr);
        
        #if TARGET_OS_SIMULATOR
            NSLog(@"Running in simulator - DNS protocol detection not supported");
        #else
            if (!isDoH) {
                NSLog(@"DoH not detected, falling back to local DNS");
                dispatch_async(dispatch_get_main_queue(), ^{
                    // Disable DoH and use LocalDNS
                    nw_privacy_context_require_encrypted_name_resolution(NW_DEFAULT_PRIVACY_CONTEXT, false, nil);
                });
            }
        #endif  
    }
    
  ......
Note
  • If "Domain Resolution Scope" is set to "Domains in the domain list".

    • For domains not in the domain list, the dnsProtocolStr in the example prints "Unknown (0)".

    • For domains in the domain list, the dnsProtocolStr in the example prints "HTTPS/DoH (4)".

  • If "Domain Resolution Scope" is set to "All domains".

    • For domains in the blacklist, the dnsProtocolStr in the example will print "Unknown (0)".

    • For domains not in the blacklist, the dnsProtocolStr in the example will print "HTTPS/DoH (4)".

Configure system-level DoH

You can configure DoH at the system level through iOS configuration profiles. System-level DoH affects all apps on the device.

To configure the DoH address:

  1. Replace the DoH address in the following sample and save it as a .mobileconfig file, such as my_company_doh.mobileconfig.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>PayloadContent</key>
   <array>
      <dict>
         <key>DNSSettings</key>
         <dict>
            <key>DNSProtocol</key>
            <string>HTTPS</string>
            <key>ServerURL</key>
            <!---- Replace the address here with the DoH access address --->
            <string>https://1xxxx3.aliyunhttpdns.com/dns-query</string>
         </dict>
         <key>PayloadDescription</key>
         <string>Configures iOS to use EMAS HTTPDNS DoH</string>
         <key>PayloadDisplayName</key>
         <string>EMAS HTTPDNS DoH</string>
         <key>PayloadIdentifier</key>
         <string>com.apple.dnsSettings.managed.9B498EC0C-EF6C-44F0-BFB7-0000658B99AC</string>
         <key>PayloadType</key>
         <string>com.apple.dnsSettings.managed</string>
         <key>PayloadUUID</key>
         <string>465AB183-5E34-4794-9BEB-B5327CF61F27</string>
         <key>PayloadVersion</key>
         <integer>1</integer>
         <key>ProhibitDisablement</key>
         <false/>
      </dict>
   </array>
   <key>PayloadDescription</key>
   <string>Adds EMAS HTTPDNS DoH configuration to iOS</string>
   <key>PayloadDisplayName</key>
   <string>EMAS HTTPDNS DoH Configuration</string>
   <key>PayloadIdentifier</key>
   <string>com.emas.apple-dns</string>
   <key>PayloadRemovalDisallowed</key>
   <false/>
   <key>PayloadType</key>
   <string>Configuration</string>
   <key>PayloadUUID</key>
   <string>130E6D6F-69A2-4515-9D77-99342CB9AE76</string>
   <key>PayloadVersion</key>
   <integer>1</integer>
</dict>
</plist>
  1. Upload my_company_doh.mobileconfig to a file server, or send it to the iOS device by email.

  2. Download my_company_doh.mobileconfig on the iOS device through a browser or email client.

  3. Install this profile in Settings > General > VPN & Device Management.