December 24, 2019

GUIDE: Block advertisements at DNS Level

Securely host your own DNS server to enhance web browsing experience, no VPN required. Accelerate your connection.

Block advertisements at DNS level with Pi-hole without a VPN

Pi-hole is a DNS level advertisement blocker, built for use on Raspberry Pis. However, it can also be set up on any VPS and restricted using a firewall to ensure that only you have access to use it (a static IP is required, or at least, if you're using a dynamic one, don't turn your router off often).

To begin, we will be using Debian-based configuration (for this tutorial, Ubuntu 18.04.3 LTS) so expect apt rather than yum as the package manager. Likewise, uncomplicated firewall (ufw) will be used, but if you're using Red Hat Enterprise Linux (RHEL), it's possible to use the raw IPTables commands instead.

Likewise, we're going to use nginx and as a way of accessing and restricting access to our admin panel. Let's Encrypt will generate the SSL certificates via

What you need to know

For this tutorial, we will need:

  • Your WAN static or IP address that doesn't change very often; we will use 123.456.789.101
  • Your server IP address; we will use 101.987.654.321
  • A domain or subdomain with an A record pointing to your server IP; we will use

Setting up Pi-hole

To begin with, you will need to log in to your server. It's recommended you use key authentication rather than passwords and also restrict your SSH port to only your IP address (as well as changing it to a non-default one for extra security).

We will be using the automated installer to help guide us through the necessary steps of setting up pi-hole, so go right ahead and run the following in your terminal:

apt update && apt -y upgrade && apt install -y git nginx php7.2-fpm
git clone --depth 1 Pi-hole
cd "Pi-hole/automated install/"
sudo bash

This will install Pi-hole and then prompt with options when it's ready.

Pi-hole configuration

For the purposes of this tutorial, we are going to configure Pi-hole using:

Select upstream DNS provider -  Choose Cloudflare

Select block lists - Tab to OK

Select protocols - Tab to OK

Do you want to use your current network settings? Tab to Yes

Do you wish to install the web admin interface?
Tab to OK with 'On (recommended)' selected

Do you wish to install the web server (lighthttpd)?
Select 'OFF' using spacebar - (Down Arrow + Space)
Tab to OK

Do you want to log queries? - Tab to OK

Select a privacy mode - Tab to OK

We have detected firewall: install Pi-hole default firewall results?
Tab to NO

Pi-hole configuration complete

Now that Pi-hole has configured everything for us, you should be shown something similar to the following (where the IP addresses reflect that of your server):

| Configure your devices to use the Pi-hole as their DNS server      │ 
│ using:                                                             │ 
│                                                                    │ 
│ IPv4:        101.987.654.321                                       │ 
│ IPv6:        Not Configured                                        │ 
│                                                                    │ 
│ If you set a new IP address, you should restart the Pi.            │ 
│                                                                    │ 
│ The install log is in /etc/pihole.                                 │ 
│                                                                    │ 
│ View the web interface at http://pi.hole/admin or                  │ 
│ http://101.987.654.321/admin                                       │ 
│                                                                    │ 
│ Your Admin Webpage login password is PASSWORD                      |

It's important now to take a note of the password in your password manager, as we will be using that soon.  This can be disabled and changed by:

pihole -a -p

Uncomplicated Firewall (ufw) configuration

To ensure that we don't get servers checking for open DNS gateways on our server, we need to configure the firewall to only allow port 53 for us.

Firstly, let's ensure that the default configuration denies all incoming traffic, then allow only our IP address for both port 22 and 53:

ufw default deny incoming
ufw allow from 123.456.789.101 to any port 22
ufw allow from 123.456.789.101 to any port 53
ufw enable
Make sure to change the IP

You may have configured port 22 separately, and if you have, it's best to omit from the step above, but as we're working with firewall, it's important to not lock ourselves out.

Then, we are going to allow port 80 for HTTP traffic and port 443 for HTTPS:

ufw allow 80
ufw allow 443

Great. That's our firewall set up and Pi-hole configuration. Now we need a way of accessing our admin panel.

Setting up nginx and php

For this tutorial, let's keep things simple. The aim is to simply set up our admin panel with an IP restriction.

This first thing to do is enable nginx and php at boot:

systemctl enable nginx && systemctl enable php7.2-fpm

Configuring nginx

Before we do anything further, we're going to remove the default server from serving files. This is so that we can point domains to our web server in the future and not have them serve anything.

To do this, unlink the default symlink in sites-enabled:

sudo unlink /etc/nginx/sites-enabled/default

Set up a default blackhole

Let's set up our configurations, our first one is simply going to be a blackhole for all content:

To do this, let's create /etc/nginx/sites-available/default-blackhole:

nano /etc/nginx/sites-available/default-blackhole

Then paste in:

server {
  listen      80 default_server;
  server_name _;
  access_log  off;
  return      444;

This returns a nginx specific 444 status code to close the connection and not log the request.

Create a symlink as follows.

sudo ln -s /etc/nginx/sites-available/default-blackhole /etc/nginx/sites-enabled/

Now, we need to create our site:

nano /etc/nginx/sites-available/
Remember to change this domain

For now, this is just our HTTP configuration, so we have something to Let's Encrypt to match against when generating the SSL certificate.

server {
  listen 80;

  # For Let's Encrypt, this needs to be served via HTTP
  location /.well-known/acme-challenge/ {
    # Specify here where the challenge file is placed
    root /var/www/html/;
    # Allow all
    allow all;
  # This is the location block invalid requests will be routed to
  location @blackhole {
    return 444;

  # This block redirects all valid requests to HTTPS, or invalid to @blackhole
  location / {
    # Route all errors to @blackhole
    error_page 403 404 500 502 503 504 =444 @blackhole;

    # IP restriction
    allow 123.456.789.101;
    deny all;

    # Return 301 if the client is allowed
    return 301$request_uri;
Change - 'server_name', 'allow', 'return 301'

So far so good, we have our configuration set up. Though, it's not actually available for nginx to see at the moment, so we need to change that to allow it to available in sites-enabled:

Create a symlink as follows.

sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
Remember to change this domain

You should now be able to test your configuration with:

nginx -t

If you don't have any errors, go ahead and reload, otherwise, take some time to check what's going wrong and fix the errors.

To reload:

nginx -s reload

Now let's move on to SSL.

Configuring SSL

Now, to be able to set up SSL certificates, we need to install

curl | sh

While we're working with SSL, let's set up our holding directories for our certificates:

mkdir -p /etc/nginx/ssl
sudo chown -R root:root /etc/nginx/ssl
sudo chmod -R 600 /etc/nginx/ssl

This creates an SSL directory in the nginx configuration folder and restricts its access for other users.

Generate an SSL certificate

Now that is installed and our folders are configured, we are ready to generate our first SSL certificate.

You should reload your bash, or log out and in again, to ensure any previous changes to the bash profile are reflected before moving on.

Then, when you're ready, run the following:

mkdir -p /etc/nginx/ssl/acme/ --issue -d -w /var/www/html --install-cert -d \
--key-file       /etc/nginx/ssl/acme/  \
--fullchain-file /etc/nginx/ssl/acme/ \
--reloadcmd     "service nginx reload"
Remember to change the domain examples

The first line creates a directory if it's doesn't exist, then the second line requests the certificate, matching the root from nginx to the web root in the request. The third line installs the certificate to our nginx folder and tells nginx to perform a reload on renewal. This is important to ensure that our updated certificate is reflected to the end client.

Generate dhparams

Next, we should generate a strong dhparams file.

mkdir -p /etc/nginx/conf.d/shared
openssl dhparam -out /etc/nginx/conf.d/shared/dhparams.pem 2048

This may take a while so feel free to grab a cup of tea and come back.

Restricting SSL access

We're also going to ensure that only strong protocols are available using a strong SSL profile.

The below snippet is important in restricting our configuration, so we will need this now to configure our secure connection.

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/nginx/conf.dshared/dhparams.pem;

# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;

Adding SSL to our admin panel

This is where we will set up the proxy to Pi-hole to see our statistics. This will also help us determine if Pi-hole is running on our client, which will set up in a later step soon.

Let's enter back to our /etc/nginx/sites-available/ file:

nano /etc/nginx/sites-available/
Remember to change this domain

Then, append (add below) the following:

server {
  listen 443 http2 ssl;
  root /var/www/html/;

  ssl_certificate /etc/nginx/ssl/acme/;
  ssl_certificate_key /etc/nginx/ssl/acme/;

  location @blackhole {
    return 444;

  error_page 403 404 =444 @blackhole;

  allow 123.456.789.101;
  deny all;
 # PHP
 location ~ \.php$ {
  include fastcgi.conf;

  fastcgi_pass_header Set-Cookie;
  fastcgi_pass_header Cookie;
  fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

  fastcgi_read_timeout 60000;
  include fastcgi_params;

  fastcgi_pass unix:/run/php/php7.2-fpm.sock;

  # SSL
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off;

  # Diffie-Hellman parameter for DHE ciphersuites
  ssl_dhparam /etc/nginx/conf.d/shared/dhparams.pem;

  # Mozilla Intermediate configuration
  ssl_protocols TLSv1.2 TLSv1.3;

  location / {
    expires max;
    try_files $uri $uri/ =404;

  location /*.js {
    index pihole/index.js;

  location /admin {
    root /var/www/html;
    index index.php index.html index.htm;
Change - 'server_name', 'ssl_certificate', 'ssl_certificate_key', 'allow'

This works much the same way as our HTTP configuration but with an additional layer of security for SSL. Note also how we have an IP restriction here to ensure anyone going to the HTTPS version is also blocked (unless their IP matches the allowed one).

This concludes the configuration required for nginx, so we should now be able to test and reload:

nginx -t

If you have no errors, reload:

nginx -s reload

Your Pi-hole admin interface is now available on and you can log in with the password you noted earlier.

Setting the DNS server

For best results, you should set the DNS server at your router level. It's recommend to have one fallback DNS server in the case of your server not being online (as you will be unable to access the Internet otherwise!), but that is up to you.

Otherwise, some handy guides for device specific DNS servers are available below:

Suggestions and comments

– Follow up post soon on how to implement DNS-over-HTTPS using cloudflared
– My adlists.list | whitelist.txt - Directory: /etc/pihole
– Remember to delete the installation files, should you need to

Let me know what you think on the Discord

Related articles
Download YouTube Videos and Playlists

Guide | Insight | Life | Linux | Tor