Setup publicly accessible services in cloud securely with reverse proxy and SSL certificate

Before we proceed with how to setup cloud/self hosted services securely, let’s go over some of the things that would be needed for this tutorial.Also the installations command is given keeping Debian like systems in mind, but can easily be found for other systems with a simple web search.

  • Docker : Install docker and docker-compose
    • sudo apt install docker.io
    • sudo apt install docker-compose-plugin
    • sudo usermod -aG docker $USER
    • newgrp docker
  • In firewall configurations, open port 443 to internet, can be done either of given ways:
    • sudo ufw allow https (for homeserver)
    • Open port 443 in cloud firewall/network (for cloud setup)
  • Also open port 80 in cloud firewall/network if using http for domain validation.
  • Either buy a domain through domain providers or visit Duckdns to get free dynamic dns.

Setting up SWAG container

SWAG sets up an Nginx webserver and reverse proxy with php support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let’s Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.

Here, I will be giving two swag.yml docker-compose files, which can be used alternatively depending upon whether you use some domain providers or duckdns.

Using Domain Providers

version: "2.1"
services:
  swag:
    image: lscr.io/linuxserver/swag
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000 # your user and group ids
      - PGID=1000 # get it with typing `id` in terminal
      - TZ=Asia/Kolkata # Docker available time zone regions
      - URL=your-domain.com  # the domain name that you bought
      - SUBDOMAINS=www,jellyfin,code-server # the subdomains which serves the specific services
      - VALIDATION=http
    volumes:
      - ./swag:/config
    ports:
      - 443:443
      - 80:80
    restart: unless-stopped

We will be using http validation to validate domain ownership. Visit your domain providers console’s manage dns section and follow the given steps to configure it correctly:

  • configure A records, add:
    • www . your-domain.com
    • your-domain.com
  • configure CNAME for each service you want to run, for each CNAME we need to configure hostname and value as:
    • CNAME (for service 1 : let’s say code-server)
      • Hostname : code-server.your-domain.com
      • Value : www . your-domain.com
    • CNAME (for service 2 : let’s say jellyfin)
      • Hostname : jellyfin.your-domain.com
      • Value : www . your-domain.com
    • In similar way, we can add as many as CNAMEs as number of services.

Using Dynamic DNS(DuckDns)

---                                                 
version: "2.1"                                      
services:                                           
  swag:                                             
    image: lscr.io/linuxserver/swag                 
    container_name: swag                            
    cap_add:                                        
      - NET_ADMIN                                   
    environment:                                    
      - PUID=1000                                   
      - PGID=1000                                   
      - TZ=Asia/Kolkata                             
      - URL=your-domain.duckdns.org                 
      - SUBDOMAINS=wildcard                         
      - VALIDATION=duckdns                          
      - DUCKDNSTOKEN=97654867496t4657382648659765854
    volumes:                                        
      - ./swag:/config                              
    ports:                                          
      - 443:443                                     
      - 80:80                                       
    restart: unless-stopped

Here, we won’t need to do anything if we are following default container names, so visit following subdomain after setup for:

  • code-server : code-server.your-domain.duckdns.org
  • jellyfin : jellyfin.your-domain.duckdns.org

Now, following either one of the way from above , we are ready to fire up our swag container, we can do so by running:

docker compose -f swag.yml up -d

Only if the domain name configurations are done properly the nginx server on swag container will start.

Check docker logs swag -f to verify if swag is successfully running without errors, it should have printed `server ready` otherwise reconfigure swag yaml file and domain name and recreate the swag container.

Before moving ahead, we need to create a user defined network and add swag container to that network, also we would add all subsequent docker containers to this network for inter-network connectivity. To do this:

docker network create -d bridge my_network
docker network connect my_network containerId

Setting up Individual Apps

  1. Setting up Jellyfin media server along with transmission torrent(transmission won’t be open to internet in this example), create a jellyfin.yml file with following content:
---                                                      
version: "2.1"                                           
services:                                                
  transmission:                                          
    image: lscr.io/linuxserver/transmission:latest       
    container_name: transmission                         
    environment:                                         
      - PUID=1000                                        
      - PGID=1000                                        
      - TZ=Asia/Kolkata                                  
      - TRANSMISSION_WEB_HOME= #optional                 
      - USER=admin #optional                             
      - PASS=password #optional                          
      - PEERPORT=51413                                   
    volumes:                                             
      - ./transmission/data:/config                      
      - ./shared/downloads:/downloads                    
    ports:                                               
      - 9091:9091                                        
      - 51413:51413                                      
      - 51413:51413/udp                                  
    restart: unless-stopped                              
  jellyfin:                                              
    image: lscr.io/linuxserver/jellyfin:latest           
    container_name: jellyfin                             
    environment:                                         
      - PUID=1000                                        
      - PGID=1000                                        
      - TZ=Asia/Kolkata                                  
      - JELLYFIN_PublishedServerUrl=192.168.0.5 #optional
    volumes:                                             
      - ./jellyfin/config:/config                        
      - ./shared/downloads:/data/to_watch                
    ports:                                               
      - 8096:8096                                        
      - 8920:8920 #optional                              
      - 7359:7359/udp #optional                          
      - 1900:1900/udp #optional                          
    restart: unless-stopped                              

now enter inside swag container using command ,

docker exec -it swag_container_id bash

and do following mentioned things:

  • cd /config/nginx/proxy-confs/
  • mv jellyfin.subdomain.conf.sample jellyfin.subdomain.conf
  • restart the swag container

Also add this container to user defined bridge network:

docker network connect my_network jellyfin_containerId

2. Setting up code-server with password authentication, create a code-server.yml with below content:

---
version: "2.1"
services:
  code-server:
    image: lscr.io/linuxserver/code-server:latest
    container_name: code-server
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Kolkata
      - PASSWORD=password1
      - SUDO_PASSWORD=password2
      - DEFAULT_WORKSPACE=/config/workspace #optional
    volumes:
      - ./code-server/config:/config
    ports:
      - 8443:8443
    restart: unless-stopped

Do again same as above as entering in swag container and moving files and restarting it along with adding code-server container to the user defined bridge network.

Now, we are done, we can access our containers now and they will look something like this:

Jellyfin: visit https://jellyfin.your-domain.com

Code Server: visit https://code-server.your-domain.com

In similar ways, we can host numerous services securely with very minimal attack surface available to exploit by malicious entities.

IMPORTANT – As you keep on adding more services, keep in mind to append them to swag container’s SUBDOMAINS environment variable and recreate the swag container.

Please refer linuxserver.io for even more awesome services and more detailed explanation for swag .

Happy hacking !

Setting up a VPN server with adblocker in Cloud (Pi-hole + PiVPN)

What is Pi-hole?

The Pi-hole is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software. We will use Pi-hole to create a adblocker for our devices .

What is PiVPN?

PiVPN is one of the simplest way to setup and manage a VPN .

What you need ?

  • A linux Cloud Server(VPS)

What we are making?

We are going to create our wireguard VPN server with Pi-hole as an adblocker to block all those unwanted ads from all of our devices.

Step 1: Creating and Configuring Linux Virtual Machine

Create a Linux virtual machine with minimal configuration . I am using ubuntu (1 GB memory , 1v CPU). we will configure firewall by opening necessary ports on it as follows.

Allowed ports in server firewall

step 2: Setting up Wireguard using PiVPN

Simply run the command to install pivpn:

curl -L https://install.pivpn.io | bash

Choose the default setting mostly , choose your favorite dns provider , and complete the setup.

step 3: Modifying wg0.conf file

open wg0.conf file in your favorite editor :

sudo nano /etc/wireguard/wg0.conf

and add these two lines:

PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

So , it looks roughly like this:

/etc/wireguard/wg0.conf

Now restart wireguard like :

sudo systemctl restart wg-quick@wg0.service

enable wireguard on boot:

sudo systemctl enable wg-quick@wg0.service

Step 4: Adding a Client Configuration

$ pivpn add

if you want to connect using a mobile client , you can also generate a qr code after adding a client:

$ pivpn -qr

you can transfer the .conf file to your local machine using tools such as scp , and running this command on your local machine will let you use the remote server as your vpn:

sudo install -o root -g root -m 600 Downloads/random1.conf /etc/wireguard/wg0.conf

Now for later step to your adblocker to work effectively , you need to edit the .conf file you transferred to your local machine and change the dns server to 10.6.0.1 .

It would look something like this:

Changing DNS to 10.6.0.1 is very important for adblocker to work properly.

Step 5: Installing docker

sudo apt install docker.io

adding the user to docker group to run command without sudo:

sudo usermod -aG docker azureuser
newgrp docker

Save and run this script ( if your wireguard IP is not 10.6.0.1 , modify accordingly):

# https://github.com/pi-hole/docker-pi-hole/blob/master/README.md

docker run -d \
    --name pihole \
    -p 10.6.0.1:53:53/tcp -p 10.6.0.1:53:53/udp \
    -p 10.6.0.1:80:80 \
    -p 10.6.0.1:443:443 \
    -e TZ="America/Chicago" \
    -v "$(pwd)/etc-pihole/:/etc/pihole/" \
    -v "$(pwd)/etc-dnsmasq.d/:/etc/dnsmasq.d/" \
    --dns=127.0.0.1 --dns=1.1.1.1 \
    --restart=unless-stopped \
    pihole/pihole:latest

printf 'Starting up pihole container '
for i in $(seq 1 20); do
    if [ "$(docker inspect -f "{{.State.Health.Status}}" pihole)" == "healthy" ] ; then
        printf ' OK'
        echo -e "\n$(docker logs pihole 2> /dev/null | grep 'password:') for your pi-hole: https://${IP}/admin/"
        exit 0
    else
        sleep 3
        printf '.'
    fi

    if [ $i -eq 20 ] ; then
        echo -e "\nTimed out waiting for Pi-hole start start, consult check your container logs for more info (\`docker logs pihole\`)"
        exit 1
    fi
done;

After this your Pi-hole+PiVPN server will be running perfectly. You can change the password of Pi-hole web interface by running this command after entering into docker container:

docker exec -it pihole bash    # getting a terminal in docker container

pihole -a -p             # to change the password

NOTES to configure client properly :

  • Change DNS in .conf file of client to , DNS=10.6.0.1
  • Change the wifi property of clients when connected to router , replacing default dns by 10.6.0.1 .
  • If your browser uses DNS over https , then the pihole(ad blocking feature) won’t work properly for that specific browser(although vpn will work fine).

Also , try to flush dns cache if needed:

sudo systemd-resolve --flush-caches

Finally verify that DNS is set to 10.6.0.1 by using dig or nslookup command. Also enable and install wireguard in client devices . For wireguard installation refer this guide.

As we can see our vpn + adblocker setup is working perfectly:

You can add more blacklists and filters to pi-hole from web admin interface. Also you can change many properties of it from there.

I hope this guide has been helpful ! For any query , feel free to comment below 🙂

Design a site like this with WordPress.com
Get started