Intro
Local/Static Domain Filter
Remote Category
Fortiguard-based Categories
Domains Feed
IP addresses feed
DNS Translation
Applying the DNS Filter Profile on the Fortigate Interface
Protecting Internal DNS Server
Inspecting Encrypted DNS Traffic
Debug and Verification
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.
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.
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:
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:
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:
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:
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:
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:
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:
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:
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:
Pinging vpn.yurisk.com [52.58.153.81] with 32 bytes of data:
And after:
Pinging vpn.yurisk.com [10.100.102.17] with 32 bytes of data:
Configuration on CLI:
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:
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):
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:
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:
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.
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:
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:
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:
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
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.
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.
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 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_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/