Skip to content

Instantly share code, notes, and snippets.

@vinayakg
Last active June 13, 2025 06:34
Show Gist options
  • Select an option

  • Save vinayakg/0153e8de9b02f064637b569d2e10ebe4 to your computer and use it in GitHub Desktop.

Select an option

Save vinayakg/0153e8de9b02f064637b569d2e10ebe4 to your computer and use it in GitHub Desktop.
Setup New PiHole
cdoh.vinayakg.dev:443 {
log {
level ERROR
output file /var/log/caddy/caddy.log {
roll_size 10MiB
roll_keep_for 3d
}
}
tls vinayak_k_g@yahoo.co.in
reverse_proxy /dns-doh-query 127.0.0.1:53 {
transport {
protocols dns
}
}
tls {
dns cloudflare 393990fe18328bccb478900b5cf9aaa61c3e3
}
route /admin* {
reverse_proxy 127.0.0.1:8080
}
respond * 404
}

Building Caddy with L4 Module on Debian Linux

Prerequisites

  1. Update your system:

    sudo apt update && sudo apt upgrade -y
    
  2. Install required packages:

    sudo apt install -y curl git build-essential
    
  3. Install Go (if not already installed):

    sudo apt install -y golang
    

    Verify Go installation:

    go version
    

    If the version is older than 1.18, you may need to install a newer version manually:

    wget https://go.dev/dl/go1.20.5.linux-amd64.tar.gz
    sudo tar -C /usr/local -xzf go1.20.5.linux-amd64.tar.gz
    

    Add Go to your PATH by adding these lines to your ~/.bashrc file:

    export PATH=$PATH:/usr/local/go/bin
    export PATH=$PATH:$(go env GOPATH)/bin
    

    Then, reload your bash profile:

    source ~/.bashrc
    

Installing xcaddy

  1. Install xcaddy:
    go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
    

Building Caddy with L4 Module

  1. Build Caddy with the L4 module:

    xcaddy build --with github.com/mholt/caddy-l4
    
  2. Verify the build:

    ./caddy version
    

(Optional) Install Caddy System-Wide

  1. Move the Caddy binary to a system-wide location:

    sudo mv caddy /usr/local/bin/
    
  2. Set the appropriate permissions:

    sudo chown root:root /usr/local/bin/caddy
    sudo chmod 755 /usr/local/bin/caddy
    
  3. (Optional) If you want Caddy to bind to privileged ports (like 80 and 443):

    sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/caddy
    

Running Caddy

  1. Create a Caddyfile in your current directory or specify a path when running Caddy.

  2. Run Caddy:

    caddy run --config /path/to/your/Caddyfile
    

Remember to replace "/path/to/your/Caddyfile" with the actual path to your Caddy configuration file.

[Unit]
Description=DoH Proxy Monitor Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/doh-monitor.sh --monitor
Restart=always
RestartSec=10
User=root
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
#!/bin/bash
# DoH Proxy Monitor and Auto-Restart Script
# Usage: ./doh-monitor.sh [--daemon]
# Configuration
DOH_PROCESS_NAME="doh-proxy"
DOH_HOST="cdoh.vinayakg.dev"
DOH_PATH="/Qz4hH-dns-query"
DOH_PORT="443"
CHECK_INTERVAL=60 # Check every 60 seconds
MAX_RESPONSE_TIME=10 # Max response time in seconds
LOG_FILE="/var/log/doh-monitor.log"
PID_FILE="/var/run/doh-monitor.pid"
# DoH command to restart (adjust as needed)
DOH_COMMAND="/usr/local/bin/doh-proxy -u 127.0.0.1:53 -l 0.0.0.0:443 -H $DOH_HOST -p /Qz4hH-dns-query --tls-cert-key-path /etc/letsencrypt/live/$DOH_HOST/privkey.pem --tls-cert-path /etc/letsencrypt/live/$DOH_HOST/fullchain.pem"
# Logging function
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Check if process is running
is_process_running() {
pgrep -f "$DOH_PROCESS_NAME" > /dev/null
return $?
}
# Check if DoH service is responding
is_doh_responding() {
local response
local exit_code
# Test DoH endpoint with timeout
response=$(curl -s --max-time "$MAX_RESPONSE_TIME" \
-H "accept: application/dns-json" \
"https://$DOH_HOST$DOH_PATH?name=google.com&type=A" 2>&1)
exit_code=$?
#if [ $exit_code -eq 0 ] && echo "$response" | grep -q "Answer\|Status"; then
if [ $exit_code -eq 0 ]; then
return 0 # Success
else
log_message "DoH health check failed: curl exit code $exit_code, response: $response"
return 1 # Failed
fi
}
# Kill stuck DoH process
kill_doh_process() {
local pids
pids=$(pgrep -f "$DOH_PROCESS_NAME")
if [ -n "$pids" ]; then
log_message "Killing stuck DoH processes: $pids"
# First try graceful termination
kill $pids
sleep 5
# Force kill if still running
pids=$(pgrep -f "$DOH_PROCESS_NAME")
if [ -n "$pids" ]; then
log_message "Force killing DoH processes: $pids"
kill -9 $pids
sleep 2
fi
fi
}
# Start DoH process
start_doh_process() {
log_message "Starting DoH proxy: $DOH_PROCESS_NAME"
#nohup $DOH_COMMAND > /dev/null 2>&1 &
systemctl start $DOH_PROCESS_NAME
sleep 3 # Give it time to start
if is_process_running; then
local pid=$(pgrep -f "$DOH_PROCESS_NAME")
log_message "DoH proxy started successfully with PID: $pid"
return 0
else
log_message "Failed to start DoH proxy"
return 1
fi
}
# Main monitoring function
monitor_doh() {
log_message "DoH monitor started (PID: $$)"
while true; do
if ! is_process_running; then
log_message "DoH process not running, starting..."
start_doh_process
else
# Process is running, check if it's responding
if ! is_doh_responding; then
log_message "DoH process stuck/not responding, restarting..."
kill_doh_process
sleep 5
start_doh_process
else
log_message "DoH service is healthy"
fi
fi
sleep "$CHECK_INTERVAL"
done
}
# Daemon mode functions
start_daemon() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "Monitor daemon is already running (PID: $(cat "$PID_FILE"))"
exit 1
fi
echo "Starting DoH monitor daemon..."
nohup "$0" --monitor > /dev/null 2>&1 &
echo $! > "$PID_FILE"
echo "Monitor daemon started with PID: $!"
}
stop_daemon() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "Stopping monitor daemon (PID: $pid)..."
kill "$pid"
rm -f "$PID_FILE"
echo "Monitor daemon stopped"
else
echo "Monitor daemon not running"
rm -f "$PID_FILE"
fi
else
echo "Monitor daemon not running (no PID file)"
fi
}
status_daemon() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "Monitor daemon is running (PID: $(cat "$PID_FILE"))"
# Show recent log entries
if [ -f "$LOG_FILE" ]; then
echo "Recent log entries:"
tail -10 "$LOG_FILE"
fi
else
echo "Monitor daemon is not running"
if [ -f "$PID_FILE" ]; then
rm -f "$PID_FILE"
fi
fi
}
# Handle command line arguments
case "${1:-}" in
--daemon)
start_daemon
;;
--stop)
stop_daemon
;;
--status)
status_daemon
;;
--monitor)
# Internal flag for daemon mode
monitor_doh
;;
--test)
echo "Testing DoH service..."
if is_process_running; then
echo "✓ DoH process is running"
else
echo "✗ DoH process is not running"
fi
if is_doh_responding; then
echo "✓ DoH service is responding"
else
echo "✗ DoH service is not responding"
fi
;;
*)
cat << EOF
DoH Proxy Monitor Script
Usage: $0 [OPTION]
Options:
--daemon Start monitor as daemon
--stop Stop monitor daemon
--status Show daemon status and recent logs
--test Test DoH service health
--monitor Run monitoring loop (internal use)
Configuration:
Host: $DOH_HOST
Path: $DOH_PATH
Check interval: $CHECK_INTERVAL seconds
Response timeout: $MAX_RESPONSE_TIME seconds
Log file: $LOG_FILE
Examples:
$0 --daemon # Start monitoring in background
$0 --test # Test if DoH is working
$0 --status # Check monitor status
$0 --stop # Stop the monitor
EOF
;;
esac
[Unit]
Description=DNS over HTTPS server proxy
After=syslog.target network-online.target
[Service]
Type=simple
Environment="RUST_LOG=debug"
ExecStart=
ExecStart=/usr/local/bin/doh-proxy -u 127.0.0.1:53 -l 0.0.0.0:443 -H cdoh.vinayakg.dev -p /Qz4hH-dns-query --tls-cert-key-path /etc/letsencrypt/live/cdoh.vinayakg.dev/privkey.pem --tls-cert-path /etc/letsencrypt/live/cdoh.vinayakg.dev/fullchain.pem
Restart=on-failure
RestartSec=10
KillMode=process
[Install]
WantedBy=multi-user.target
# sudo adduser -m -d /home/pivg -s /bin/bash pivg
sudo adduser pivg
passwd pivg
sudo cp -r .ssh /home/pivg/
# ensure the directory ir owned by the new user
sudo chown -R pivg:pivg /home/pivg/.ssh
# make sure only the new user has permissions
sudo chmod 700 /home/pivg/.ssh
sudo chmod 600 /home/pivg/.ssh/authorized_keys
sudo usermod -aG sudo pivg
# https://humanwhocodes.com/snippets/2021/03/create-user-linux-ssh-key/
# Automatically upgrade apt packages
sudo apt update && sudo apt upgrade -y
# Install unattended upgrades
sudo apt install unattended-upgrades -y
# Enable auto updates for unattended upgrades
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | sudo debconf-set-selections
# Prevent SSH brute force attacks by installing fail2ban
sudo apt install fail2ban -y
# Install firewall ufw
sudo apt install ufw -y
# Rate limit SSH attempts
sudo ufw limit ssh/tcp
# Firewall rules
sudo ufw allow OpenSSH
sudo ufw allow dns
sudo ufw allow http
sudo ufw allow https
# Enable ufw and default to deny
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo apt install ./cloudflared-linux-amd64.deb
sudo cloudflared proxy-dns --port 54 --upstream https://1.1.1.1/.well-known/dns-query --upstream https://1.0.0.1/.well-known/dns-query
curl -sSL https://install.pi-hole.net | sudo bash
pihole setpassword Qz4hHjKn83nF
sudo vim /etc/dnsmasq.d/01-pihole.conf
server=127.0.0.1#54
sudo systemctl stop systemd-resolved.service
sudo systemctl disable systemd-resolved.service
sudo vim /etc/systemd/system/dnsproxy.service
[Unit]
Description=CloudFlare DNS over HTTPS Proxy
Wants=network-online.target
After=network.target network-online.target
[Service]
ExecStart=cloudflared proxy-dns --port 54 --upstream https://1.1.1.1/.well-known/dns-query --upstream https://1.0.0.1/.well-known/dns-query
Restart=on-abort
[Install]
WantedBy=multi-user.target
sudo systemctl enable dnsproxy.service
curl -L https://install.pivpn.io | bash
# generate a new certificate
sudo certbot certonly --webroot -w /var/www/html -d cdoh.vinayakg.dev -m vinayak_k_g@yahoo.co.in --agree-tos --rsa-key-size 4096 --non-interactive
## https://discourse.pi-hole.net/t/can-pihole-act-as-a-doh-or-dot-server/56106/3
wget https://github.com/DNSCrypt/doh-server/releases/download/0.9.12/doh-proxy_0.9.11-2_amd64.deb
sudo apt install ./doh-proxy_0.9.11-1_amd64.deb
# doh-proxy , upstream: 127.0.0.1:53, listen:0.0.0.0:3000, hostname: cdoh.vinayakg.dev, full certificate: /etc/letsencrypt/live/cdoh.vinayakg.dev/fullchain.pem, private key: /etc/letsencrypt/live/cdoh.vinayakg.dev/privkey.pem
/usr/local/bin/doh-proxy -u 127.0.0.1:53 -l 0.0.0.0:443 -H cdoh.vinayakg.dev -p /Qz4hH-dns-query --tls-cert-key-path /etc/letsencrypt/live/cdoh.vinayakg.dev/privkey.pem --tls-cert-path /etc/letsencrypt/live/cdoh.vinayakg.dev/fullchain.pem
# Test the DoH server
curl -iH 'content-type: application/dns-message' 'https://cdoh.vinayakg.dev/Qz4hH-dns-query?ct=&dns=vWYBAAABAAAAAAAAA3BicwV0d2ltZwNjb20AAAEAAQ'
# cronjob
0 0 28 1,4,7,10 * sudo certbot renew --quiet && sudo sh -c 'sudo cat /etc/letsencrypt/live/cdoh.vinayakg.dev/privkey.pem /etc/letsencrypt/live/cdoh.vinayakg.dev/fullchain.pem'| sudo tee /etc/pihole/tls.pem > /dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment