Fortigate DNS Filter - All You Need to Know (almost)
Intro
Few facts to remember:
-
The DNS query/response traffic HAS to cross the Fortigate for it to be inspected/filtered.
-
You do NOT need to set the Fortinet/FortiGuard DNS servers as DNS resolvers in Fortigate. Even better - do NOT use Fortinet DNS servers as regular DNS resolvers, you will have problems.
DNS Filtering allows us to control/filter DNS queries crossing the Fortigate from clients, this way preventing DNS resolving the names to IP addresses for malicious/unwanted domains.
As Web Filtering, DNS Filtering also gives us option to use Fortiguard-based category filtering, and Static Domain names filtering where we configure wildcard/regex domain names to match. In addition, DNS Filter has Botnet Command Center block, using dynamically updated IPs/domains list of known bad, this way blocking malware in LAN contacting them on the Internet.
Botnet C&C Block and Category-based filtering is a licensed feature - you need Web Filtering license for that. DNS Filtering uses the same FortiGuard servers and the same websites/domain ratings as the Web Filter does. The mechanism is different, though. DNS Filtering watches/controls DNS resolving queries only, not the following traffic.
THe DNS protocols supported are clear text port 53 TCP/UDP queries protocol, and DNS over TLS encrypted in TLS DNS queries on port 853 (DOT). For controlling DOT traffic the Deep SSL Inspeciton profile has to be used, and the FortiOS version should be 7.0.x or newer.
DNS Filter has 3 possible actions: Allow, Monitor, Redirect to Block Portal.
-
Redirect with Block - for bad/banned domains, DNS Filter replaces A record of the resolved bad domain with IP address of the FortiGuard server which holds the vanilla web page with the block message (in case the blocked DNS query was for the HTTP/HTTPS server so client can see this page). The non-HTTP traffic will be redirected to the same server, obvioulsy, but w/o any indication what happened - end user will see time out in her application.
-
Allow - passes the DNS query and answer unaltered, thus resolving the domain to its original IP address.
-
Monitor - well, monitors DNS traffic, logging DNS queries and responses, see below on Proxy vs Flow mode.
DNS Profile can be applied in 2 places - either in a specific Security Policy, or on the DNS server configuration for the interface, when enabling Fortigate to do recursive queries for clients on that interface.
Order of processing: Static Domains list → Remote Category → Category Fortiguard-based rating.
If the Static Domain filter action is set to Allow - Fortigate does NOT check Category-based filters for matched domains. If, on the other hand, Static Domain filter is set to Monitor, and domain is matched, then in Proxy mode it will be allowed, but in Flow mode will be sent for a check by Category-based filter.
Block for the page/domain using HTTPS will show browser error to the end user on net::ERR_CERT_COMMON_NAME_INVALID
, as the resulting block page hosted by Fortiguard (and its SSL/TLS certificate) has nothing to do with the original website the user tried to enter.
Note
|
Whatever happens in DNS Filtering has no relation to further checks by Web Filter if it is enabled in the same security rule as well. The 2 features are independent of each other. |
Note
|
All the examples below were done using Proxy mode security policy. When using Flow mode, IPS is mostly responsible for DNS filtering and the debug should also be dia ips debug enable dns. |
Local/Static Domain Filter
Just as with Web Filtering, we can manually create list of domains to match. The domains can be in the form of Simple, Wildcard, and Regex.
The Local/Static Domain filter will look like that:
-
Static Domain filter table of domains:
config dnsfilter domain-filter edit 1 set name "Auto-dnsfilter-domain-filter_uuctvw6su" config entries edit 1 set domain "*.yurisk.info" set type wildcard set action allow next edit 2 set domain "*" set type wildcard next end next edit 2 set name "Auto-dnsfilter-domain-filter_a28yutjw3" config entries edit 1 set domain "mail.ru" next edit 2 set domain "*.fox.com" set type wildcard next end next edit 3 set name "Auto-dnsfilter-domain-filter_965xz31fp" config entries edit 1 set domain "*.cisco.com" set type wildcard next edit 2 set domain ".*check.*\\.com" set type regex next end next end
-
DNS FIlter Profile, combining Static and Fortiguard-based filters:
config dnsfilter profile edit "DNSProfile" config domain-filter set domain-filter-table 3 <-- STATIC DOMAIN FILTER TABLE ID end config ftgd-dns config filters edit 1 set category 26 set action block next edit 7 set category 46 set action block next end end set log-all-domain enable set block-botnet enable <-- ENABLE C&C BLOCK next end
In logs, Security Events → DNS Query, the block (actually Action = redirect), will look:


The end user will see the default block page:

Remote Category
Fortiguard-based Categories
This one works exactly as with Web Filtering, only for DNS protocol queries. In debug output, the Fortiguard servers are called SDNS servers. By default, Fortigate uses DOH (encrypted) DNS protocol to send rating requests to SDNS server.

Domains Feed
This option enables us to specify external domain list, hosted on a web server and not on Fortiguard servers/Fortigate, for Fortigate to periodically download it and use in DNS Filter (Category Web Filtering should be enabled) to block/allow access to the domains in the list.
The list should be a text file in the format (the file name does not matter), only Simple/Wildcards names are supported:
cnn.com facebook.com *.reddit.com
After we created such threat list on external web server, we create an External Connector in the Fabric Connectors:

Where we set the URL to fetch the external threat feed, with optional authentication and configurable refresh time (default 5 mins):


After some refreshing the status turns green (connected) and we can actually see the list of domains in the feed and their status:


Now, we can see this Remote feed appear in the Categories and we can set its action to Block:

Config on CLI:
config system external-resource edit "Remote threat feed" set type domain set category 192 set resource "https://yurisk.info/threat/threat.txt" next end
Now we use this custom category (number 192, will differ per FortiOS version) with the action Block and Redirect in the Fortigaurd Category section of the DNS Filter profile:
edit "DNSProfile" config domain-filter set domain-filter-table 3 end config ftgd-dns config filters edit 9 set category 192 set action block next end end end end
IP addresses feed
This works as above but instead of the domains list, we provide to the Fortigate an externally hosted text file that lists IP addresses (specific, netwrok masked). And when Fortigate sees such IP address in DNS response, it replaces it with the default IP of Fortigaurd block page.
The configuration is the same as above Domain list, just choose in the External Connectors IP Address as a connector:

And configure with the URL of external feed to download:

The IP addresses list will look like:
192.0.64.0/18
And it will be located at https://yurisk.info/threat/ip-list.txt.
After the Fortigate successfully contacts and downloads the remote feed, we can use it in Block statement of the DNS Filter profile:

Now, we use this DNS Filter profile in a security rule, and it will block resolving to any IP in the range specified:

CLI Configuration:
config system external-resource edit "IP-block-list-feed" set type address set resource "https://yurisk.info/threat/ip-list.txt" next end
And use it in the DNS Profile:
config dnsfilter profile edit "DNSProfile" set log-all-domain enable set block-botnet enable set external-ip-blocklist "IP-block-list-feed" next end
DNS Translation
Other vendors also call it DNS Doctoring (Cisco PIX/ASA anyone?) - Fortigate checks every DNS response returning to the end client and passing the Fortigate and if the configured IP address is seen, it is replaced by another IP address we specify. It is not that much of a security measure, but is helpful when say LAN hosts try to access your corporate website resolving against external DNS servers on the Internet - the whole Internet will get as DNS response a legal IP address, while hosts behind Fortigate will see some Internal IP. Example is the best here.
Let’s say I have hosted somewhere a website named "vpn.yurisk.com" that resolves for the whole world to IP of 52.58.153.81, but for hosts behind Fortigate, I want them to connect to the internal copy of the website located at 10.100.102.17. To do so, inside the DNS Filter Profile, I do:

The end result will look:

End client before DNS Translation is applied:
C:\WINDOWS\system32>ping vpn.yurisk.com Pinging vpn.yurisk.com [52.58.153.81] with 32 bytes of data:
And after:
C:\WINDOWS\system32>ping vpn.yurisk.com Pinging vpn.yurisk.com [10.100.102.17] with 32 bytes of data:
Configuration on CLI:
config dnsfilter profile edit "DNSProfile" config dns-translation edit 1 set src 52.58.153.81 set dst 10.100.102.17 next end next end
Applying the DNS Filter Profile on the Fortigate Interface
Beyond security rules, we can apply the DNS Filter profile on the interface where (and if) we enabled DNS server to answer queries from the attached to the interface hosts. The feature functions the same, except it is less granular and is applied to ALL hosts behind the interface sending DNS queries.
Make sure to make this feature visible: System → Feature Visibility → DNS Database.
After that we go to Network → DNS Servers, and create a new one:

Choose interface we want to enable DNS server for, then enable DNS filter and pick the one:


Config on CLI:
config system dns-server edit "port2" set dnsfilter-profile "DNSProfile" next end
Protecting Internal DNS Server
Fortigate can also provide some protection to the internal DNS available to queries from the Internet, if serving as an authority DNS server for your domain. As an example let’s protect our internal DNS server 10.100.104.15 available to the Internet via VIP, external IP being 10.100.100.227 (yeah, lots of NAT), and holding the DNS zone for "yurisk.info". We want to open this DNS server to queries from ANY IP on the Internet, but also protect it from bad actors. We can apply IPS Sensor to all incoming connections, this way blocking the known exploits by signature (not shown here), and we can limit incoming DNS queries to the domain and its subdomains "yurisk.info" only by using the wildcard in the DNS Filter profile of "*.yurisk.info".
First, the VIP (nothing special here):
config firewall vip edit "VIP-DNS-Linux" set extip 10.100.100.227 <-- WAN IP OF FGT set mappedip "10.100.104.15" <-- LAN IP OF DNS set extintf "port1" set portforward enable set protocol udp set extport 53 set mappedport 53 next end
As I know for sure my domain does not include large records, no TCP is needed.
Now, I create the Static DNS Filter to allow only queries for sub/domain "yurisk.info" and block anything else:
config dnsfilter domain-filter edit 1 set name "Auto-dnsfilter-domain-filter_uuctvw6su" config entries edit 1 set domain "*.yurisk.info" <-- ALLOW QUERY OF MY DOMAIN set type wildcard set action allow next edit 2 set domain "*" <-- BLOCK ANYTHING ELSE set type wildcard (1) next end next end
-
- the default action is "block".
Next, we create DNS Filter that uses this domains list:
config dnsfilter profile edit "ProtectDNS" config domain-filter set domain-filter-table 1 end config ftgd-dns set options ftgd-disable end set log-all-domain enable next end
Or in GUI:

One tweak is a good practice here - if left as is, any query for unrelated domain will be redirected to the default Fortigaurd-hosted block page, i.e. it will be "successful" DNS-wise. And that may encourage DDoS attackers to try and use our DNS in amplification attack, thinking we are accepting queries to any domain, not good. For this, we have the option to change the default Redirect to Block action to one of the NXDOMAIN (no domain found), or SERVFAIL (authoritative server did not answer). For our purpose the 1st option is better as the attackers will most probably see this error and will understand we are NOT an open DNS resolver and it would be useless to try any further.
FGT-Perimeter (ProtectDNS) # set block-action block Return NXDOMAIN for blocked domains. redirect Redirect blocked domains to SDNS portal. <-- THE DEFAULT block-sevrfail Return SERVFAIL for blocked domains. set block-action block <-- SET TO NXDOMAIN
The final DNS Profile will look:
config dnsfilter profile edit "ProtectDNS" config domain-filter set domain-filter-table 1 end config ftgd-dns set options ftgd-disable end set log-all-domain enable set block-action block next end
And, finally, the security rule:
config firewall policy edit 8 set name "DNS incoming" set srcintf "port1" set dstintf "port2" set action accept set srcaddr "all" set dstaddr "VIP-DNS-Linux" set schedule "always" set service "ALL" set utm-status enable set inspection-mode proxy (1) set dnsfilter-profile "ProtectDNS" set logtraffic all next end
-
- for this to work, we have to use Proxy mode policy, otherwise banned requests would not be stopped.
For a test, I will try to resolve yahoo.com against the internal DNS server:
Log of the block:

Trying to query for yahoo.com, the DNS client will get NXDOMAIN as expected:

Inspecting Encrypted DNS Traffic
Fortigate starting with FOrtiOS 7.0.x can inspect encrypted DNS traffic as well. You will, obviously, need Deep SSL Inspection to be enabled on such traffic. I haven’t tested this feature yet, so cannot comment - once I will have tested it, I will write a separate blog post.
Debug and Verification
-
Display DNS Filter related logs on CLI:
exe log filter category utm-dns or exe log filter category 15
Then exe log display:
date=2025-03-18 time=10:46:38 eventtime=1742319998319056364 tz="-0700" logid="1501054802" type="utm" subtype="dns" eventtype="dns-response" level="notice" vd="root" policyid=21 poluuid="06ac0942" policytype="policy" sessionid=21428 user="localvpn1" group="ipsecgrp" srcip=192.168.17.0 srcport=64537 srccountry="Reserved" srcintf="IKEv1" srcintfrole="undefined" dstip=8.8.8.8 dstport=53 dstcountry="United States" dstintf="port1" dstintfrole="undefined" proto=17 profile="DNSProfile" xid=5287 qname="edge.microsoft.com" qtype="A" qtypeval=1 qclass="IN" ipaddr="13.107.21.239, 204.79.197.239" msg="Domain is monitored" action="pass" cat=52 catdesc="Information Technology"
-
diagnose test app dnsproxy 2 Show configs & statistics
FGT-Perimeter # diagnose test app dnsproxy 2 worker idx: 0 worker: count=1 idx=0 retry_interval=500 query_timeout=1495 DNS latency info:<-- LATENCY FOR DNS RESOLVING vfid=0 server=10.100.0.2 latency=1 updated=1274 vfid=0 server=96.45.45.45 latency=1 updated=389863 vfid=0 server=96.45.46.46 latency=3 updated=389899 SDNS latency info:<-- LATENCY OF FORTIGUARD (SDNS) QUERIES vfid=0 server=173.243.140.53 latency=2 updated=8867 vfid=0 server=139.138.105.53 latency=3 updated=8877 DNS_CACHE: alloc=90, hit=125 RATING_CACHE: alloc=165, hit=414 DNS query: alloc=0 DNS UDP: req=1762 res=1202 fwd=1112 cmp=29 retrans=25 to=18 cur=92 switched=330103 num_switched=5 v6_cur=0 v6_switched=0 num_v6_switched=0 DNS FTGD: ftg_fwd=166, ftg_res=166, ftg_retrans=0 DNS TCP: req=2, res=2, fwd=2, retrans=0, to=0 DNS TCP connections: DNS UNIX streams: cfd=36 FQDN: alloc=1 nl_write_cnt=1 nl_send_cnt=1 nl_cur_cnt=0 Botnet: searched=288 hit=0 filtered=288 false_positive=0 DNS domain filter tables: <-- STATIC DOMAIN FILTERS PRESENT AND USED vfid=0 id=1 name=Auto-dnsfilter-domain-filter_uuctvw searched=0 hit=0 vfid=0 id=3 name=Auto-dnsfilter-domain-filter_965xz3 searched=260 hit=2 DNS external domain filter tables:<-- EXTERNAL DOMAINS FEED name=Remote threat feed uuid_idx=15845 searched=307 hit=17
-
diagnose test app dnsproxy 3 Even more stats.
worker idx: 0 VDOM: root, index=0, is primary, vdom dns is enabled, pip-0.0.0.0 dns_log=1 VDOM FROM WHICH FORTIDUARD (SDNS) QUERIES ARE SENT dns64 is disabled DNS servers: DNS SERVERS FOR RESOLVING, ENCRYPTED - DNS OVER TLS by FORTINET 10.100.0.2:53 vrf=0 tz=0 encrypt=none req=628 to=0 res=628 rt=8 ready=1 timer=0 probe=0 failure=0 last_failed=0 96.45.45.45:853 vrf=0 tz=0 encrypt=dot req=2 to=2 res=2 rt=1 ready=1 timer=0 probe=0 failure=0 last_failed=0 96.45.46.46:853 vrf=0 tz=0 encrypt=dot req=1 to=1 res=1 rt=3 ready=1 timer=0 probe=0 failure=0 last_failed=0 SDNS servers: <-- SDNS SERVERS 139.138.105.53:853 vrf=0 tz=-420 encrypt=dot req=68 to=0 res=68 rt=3 ready=1 timer=0 probe=0 failure=0 last_failed=0 173.243.140.53:853 vrf=0 tz=-420 encrypt=dot req=98 to=0 res=98 rt=2 ready=1 timer=0 probe=0 failure=0 last_failed=0 ALT servers: Interface selecting method: auto Specified interface: FortiGuard interface selecting method: auto FortiGuard specified interface: DNS_CACHE: hash-size=2048, ttl=1800, min-ttl=60, max-num=5000 DNS FD: udp_s=12 udp_c=18:19 ha_c=23 unix_s=6, unix_nb_s=24, unix_nc_s=7 v6_udp_s=13, v6_udp_c=21:22, snmp=25, redir=14, v6_redir=15 DNS FD: tcp_s=27, tcp_s6=28, redir=29 v6_redir=30 DNS UNIX FD: dnsproxy_un=31 FQDN: min_refresh=60 max_refresh=3600 FGD_DNS_SERVICE_LICENSE: <-- WEB FILTERING LICENSE STATUS server=139.138.105.53:853, expiry=2038-01-02, expired=0, type=2 server=173.243.140.53:853, expiry=2038-01-02, expired=0, type=2 FGD_CATEGORY_VERSION:10 SERVER_LDB: gid=da0b, tz=-420, error_allow=0 DEFAULT REPLACEMENT IP TO REDIRECT TO BLOCK PAGE FGD_REDIR_V4:208.91.112.55 FGD_REDIR_V6:[2620:101:9000:53::55]
-
dia test app dnsproxy 15 Show the cached category responses from Fortiguard, doe snot show Remote4 feeds or Local categories, only Fortiguard-based ratings.
FGT-Perimeter # diagnose test app dnsproxy 15 worker idx: 0 SDNS rating cache: name=srtb.msn.com, category=41, ttl=10732 name=img-s-msn-com.akamaized.net, category=82, ttl=10732 name=ntp.msn.com, category=41, ttl=10731 name=sb.scorecardresearch.com, category=52, ttl=10730 name=c.msn.com, category=41, ttl=10730 name=assets.msn.com, category=41, ttl=10730 name=browser.events.data.msn.com, category=41, ttl=10730 name=array613.prod.do.dsp.mp.microsoft.com, category=52, ttl=10721 name=1742309134-2243019459389133638-1048.cu.dzen.ru, category=36, ttl=10698 name=telemetry.dzen.ru, category=36, ttl=10643 name=api.vk.com, category=37, ttl=10639 name=stacks.vk-portal.net, category=37, ttl=10639 name=vk.com, category=37, ttl=10638 name=log.strm.yandex.ru, category=25, ttl=10638 name=static.vk.com, category=37, ttl=10638 name=rs.mail.ru, category=23, ttl=10638 name=csp.yandex.net, category=41, ttl=10638
-
Real-time debug dia deb application dnsproxy -1, will show ongoing DNS Filter decisions, may show partial output if Flow mode policy used as IPS sensor is handling large part of the feature.
E.g. trying to query for A record of yahoo.com the internal DNS server:
dia deb application dnsproxy -1 dia deb enable dns_secure_log_request()-1123: id:0x0773 pktlen=50 profile=ProtectDNS ifindex=3 dns_secure_log_request()-1179: write to log: qname=yahoo.com qtype=1 dns_profile_do_url_rating()-1992: vfid=0 profile=ProtectDNS category=255 domain=yahoo.com dns_url_table_search()-1941: search domain yahoo.com in Auto-dnsfilter-domain-filter_uuctvw dns_profile_do_url_rating()-2021: found static domain filter for yahoo.com (table=Auto-dnsfilter-dom uuctvw action=2) dns_profile_do_url_rating()-2088: request filter result for yahoo.com (type=1 action=2) dns_secure_apply_action()-2229: action=2 category=0 log=0 error_allow=0 profile=ProtectDNS dns_send_error_response()-1821: domain=yahoo.com err=3 dns_send_response()-1645: domain=yahoo.com reslen=50 dns_secure_log_response()-1256: id:0x7307 domain=yahoo.com profile=ProtectDNS action=2 log=0 dns_policy_find_by_idx()-2924: vfid=0 idx=8 dns_secure_log_response()-1506: write to log: logid=54400 qname=yahoo.com
In this case I try to browse to www.reddit.com from LAN, when reddit.com is blocked in External feed:
dns_secure_log_request()-1179: write to log: qname=www.reddit.com qtype=1 dns_profile_do_url_rating()-1992: vfid=0 profile=DNSProfile category=255 domain=www.reddit.com dns_url_table_search()-1941: search domain www.reddit.com in Auto-dnsfilter-domain-filter_jv2g7v <-- SEARCH STATIC DOMAINS LIST NOW CHECKS BOTNET LIST: botnet_domain_search()-2291: domain=www.reddit.com passed botnet check FINALLY, SEARCHES CATEGORY 192 - EXTERNAL FEED: dns_profile_do_url_rating()-2042: search domain www.reddit.com in category 192 dns_profile_do_url_rating()-2047: found external domain filter for www.reddit.com (table=Remote threat feed category=192) <-- NAME OF EXTERNAL FEED CONENCTOR dns_profile_do_url_rating()-2088: request filter result for www.reddit.com (type=7 action=10) dns_secure_apply_action()-2229: action=10 category=192 log=1 error_allow=0 profile=DNSProfile
-
You may find helpful my post on Web Filtering as well here - https://yurisk.info/2025/03/13/fortigate-web-filtering-all-you-need-to-know/
I also write cheat sheets/scripts/guides to help in daily work, so make sure to check out my Github at https://github.com/yuriskinfo and https://www.linkedin.com/in/yurislobodyanyuk/