Swiss...
Running Tor Onion Services on Swiss VPS: A Complete Deployment Guide for Privacy-First Operators
Your clearnet website announces your server's IP address to every visitor, every CDN, every analytics provider, and every government that asks your upstream provider for netflow data. An onion service removes that exposure entirely. Here is how to deploy one properly on Swiss infrastructure.
October 5, 2026
by SwissLayer 20 min read
Tor Onion Services Deployment on Swiss VPS

Most privacy-focused hosting conversations stop at choosing the right jurisdiction. You pick a Swiss VPS, configure full-disk encryption, harden SSH, set up a firewall — and you call it done. The problem is that your server still sits on the clearnet. It has a public IP address. DNS records point to it. Certificate transparency logs show what domains it serves. Anyone running a network scan can find it, fingerprint it, and start building a profile of what you run and who connects to it.

Tor onion services change that equation entirely. When you run a service as a .onion address, connections happen inside the Tor network. Your server's real IP address is never exposed to the client. The client's IP address is never exposed to your server. There is no DNS resolution, no certificate authority involved, no network path that reveals the physical location of the infrastructure. The cryptographic address itself is the authentication — if you can reach the .onion address, the service is who it claims to be.

This is not about hiding. It is about architecture. Onion services are end-to-end encrypted by design, mutually authenticated without certificate authorities, and resistant to network-level surveillance that even the best firewall rules cannot prevent. If you chose privacy hosting in Switzerland because you take infrastructure security seriously, running onion services is a natural extension of that decision.

This guide covers deploying Tor onion services on a Swiss dedicated server or VPS — from initial installation through production hardening — with the operational security considerations that most tutorials skip.

Understanding Onion Services: What They Actually Do

Before touching a terminal, it is worth understanding what onion services provide at a protocol level, because the security properties are different from what most people assume.

A standard HTTPS website works like this: the client resolves your domain through DNS (visible to the resolver and anyone monitoring DNS traffic), connects to your server's IP address (visible to the ISP, upstream provider, and anyone with access to netflow data), negotiates a TLS session using a certificate signed by a certificate authority (visible in certificate transparency logs), and exchanges data. At every step, metadata leaks — who is connecting to what, when, and from where.

An onion service eliminates all of that:

• No DNS resolution. The .onion address is a cryptographic hash of the service's public key. There is no DNS query, no registrar, no WHOIS record, and no zone file sitting on a nameserver
• No IP exposure. Neither the client's IP nor the server's IP is revealed to the other party. Both sides connect through Tor circuits that terminate at rendezvous points
• No certificate authority. The .onion address IS the authentication. If you connect to abc123...xyz.onion, you are provably talking to the holder of the corresponding private key. No CA trust chain, no certificate issuance that appears in transparency logs
• End-to-end encryption. Traffic between the client and the onion service is encrypted through the entire Tor circuit. Even if someone compromises a relay, they see encrypted traffic between relays — not the actual content
• NAT traversal. Onion services work behind firewalls and NAT without port forwarding. The service creates outbound circuits to the Tor network, so no inbound ports need to be open

The v3 onion address format (introduced in 2018 and the only format currently supported) uses ED25519 public key cryptography. A v3 address looks like this: vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion — a 56-character base32-encoded string derived from the service's public key and a version byte. The length of the address is not an accident; it encodes enough cryptographic material to make address collision computationally infeasible.

Prerequisites and Server Preparation

Before installing Tor, your server needs a clean baseline. This guide assumes a Swiss VPS or dedicated server running Debian 12 (Bookworm) or Ubuntu 22.04/24.04 LTS. The principles apply to other distributions, but the package names and paths may differ.

Start with a fresh system and make sure it is updated:

# Update the system
sudo apt update && sudo apt upgrade -y

# Install essential packages
sudo apt install -y apt-transport-https curl gnupg2 unattended-upgrades

# Enable automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades

# Verify the system clock is reasonably accurate
# Tor relies on time synchronisation — skew greater than ~2 hours
# causes circuit building failures
timedatectl status
# System clock synchronized: yes

# If NTP is not running, enable it
sudo timedatectl set-ntp true

Firewall configuration matters here. For a server that ONLY serves traffic through onion services — no clearnet presence at all — you can lock down the firewall aggressively:

# Minimal firewall for onion-only server
# The only inbound port you need is SSH (for your management access)
# Tor makes outbound connections only — no inbound ports required

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh

# If you are running a clearnet+onion dual setup, you also need:
# sudo ufw allow 80/tcp
# sudo ufw allow 443/tcp

sudo ufw enable
sudo ufw status verbose

Notice what is absent: no port 80, no port 443, no port 9001, no port 9050. An onion-only service does not need ANY inbound ports beyond SSH. Tor connects outward to the network through circuits; the service is reachable via those circuits without any inbound port being open. This is a significant security advantage — your server's public IP address can have every port closed except SSH, making it effectively invisible to port scanners.

Installing Tor from the Official Repository

Always install Tor from the official Tor Project repository, not from your distribution's default packages. Distribution packages are often outdated by months or years, missing security patches and features. The Tor Project maintains their own Debian/Ubuntu repository with timely updates:

# Add the Tor Project signing key
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
  | gpg --dearmor \
  | sudo tee /usr/share/keyrings/tor-archive-keyring.gpg > /dev/null

# Verify the key fingerprint
gpg --no-default-keyring \
  --keyring /usr/share/keyrings/tor-archive-keyring.gpg \
  --fingerprint
# Should show: A3C4 F0F9 79CA A22C DBA8  F512 EE8C BC9E 886D DD89

# Add the repository (adjust for your distribution)
# For Debian 12 (Bookworm):
echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org bookworm main" \
  | sudo tee /etc/apt/sources.list.d/tor.list

# For Ubuntu 22.04 (Jammy):
# echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org jammy main" \
#   | sudo tee /etc/apt/sources.list.d/tor.list

# Install Tor and the keyring package
sudo apt update
sudo apt install -y tor deb.torproject.org-keyring

# Verify installation
tor --version
# Tor version 0.4.8.x

At this point Tor is installed but not configured for onion services. The default configuration runs Tor as a client — it can route traffic through the Tor network but does not publish any services. Let's change that.

Configuring Your First Onion Service

The Tor configuration lives in /etc/tor/torrc. For an onion service that fronts a web server running on localhost, the configuration is straightforward:

# /etc/tor/torrc — Onion service configuration
# Back up the original first
sudo cp /etc/tor/torrc /etc/tor/torrc.backup

# Edit the configuration
sudo nano /etc/tor/torrc

Add the following to your torrc:

# === Onion Service Configuration ===

# Directory where Tor stores the onion service keys and hostname
# This directory will be created automatically with restricted permissions
HiddenServiceDir /var/lib/tor/my-onion-service/

# Map port 80 on the onion address to port 8080 on localhost
# Format: HiddenServicePort VIRTUAL_PORT TARGET_HOST:TARGET_PORT
HiddenServicePort 80 127.0.0.1:8080

# For HTTPS (optional — encryption is redundant over .onion but
# some applications require it):
# HiddenServicePort 443 127.0.0.1:8443

# === Performance Tuning ===

# Number of introduction points (default 3, max 20)
# More introduction points = more redundancy and faster initial connections
# But also slightly increases the service's fingerprint on the network
HiddenServiceNumIntroductionPoints 3

# === Security Options ===

# Run Tor as a dedicated user (default on Debian/Ubuntu)
User debian-tor

# Disable Tor relay functionality — this server is a service, not a relay
ORPort 0
# Do not contribute bandwidth to the Tor network from this server
# Running a relay on the same server as an onion service is an OpSec risk

# SOCKS port for local use (if the server needs to make outbound Tor connections)
SocksPort 9050

# Log level — notice is appropriate for production
Log notice file /var/log/tor/notices.log

Now restart Tor and retrieve your onion address:

# Restart Tor to apply the configuration
sudo systemctl restart tor

# Check that Tor started successfully
sudo systemctl status tor
# Active: active (running)

# Wait a few seconds for the onion service to publish its descriptor
# Then read your .onion address
sudo cat /var/lib/tor/my-onion-service/hostname
# Output: something like vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion

# Verify the service directory permissions
ls -la /var/lib/tor/my-onion-service/
# drwx--S--- 2 debian-tor debian-tor 4096 Oct  5 08:00 .
# -rw------- 1 debian-tor debian-tor   63 Oct  5 08:00 hostname
# -rw------- 1 debian-tor debian-tor   96 Oct  5 08:00 hs_ed25519_public_key
# -rw------- 1 debian-tor debian-tor   64 Oct  5 08:00 hs_ed25519_secret_key

The hs_ed25519_secret_key file is the most critical file on your server. Whoever has this key controls the onion address. If it is compromised, an attacker can impersonate your service. If it is lost, your onion address is gone forever. Back it up securely — encrypted, offline, separate from your server.

Setting Up Nginx as the Backend Web Server

Your onion service needs a web server to actually serve content. Nginx is the natural choice, but the configuration for an onion service differs from a standard clearnet setup in important ways:

# Install Nginx
sudo apt install -y nginx

# Remove the default site
sudo rm /etc/nginx/sites-enabled/default

Create the onion service virtual host:

# /etc/nginx/sites-available/onion-service
# Nginx configuration for a Tor onion service

server {
    # Listen ONLY on localhost — never bind to 0.0.0.0
    # Tor connects to this port through the local loopback
    listen 127.0.0.1:8080;

    # The server_name is your .onion address
    # Replace with your actual address from the hostname file
    server_name vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion;

    # Document root
    root /var/www/onion;
    index index.html;

    # === Privacy-First Headers ===

    # Prevent browsers from making clearnet requests for resources
    # CSP restricts all resources to same-origin (the .onion address)
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;

    # Prevent clickjacking
    add_header X-Frame-Options "DENY" always;

    # Prevent MIME type sniffing
    add_header X-Content-Type-Options "nosniff" always;

    # Referrer policy — never leak the .onion address as a referrer
    add_header Referrer-Policy "no-referrer" always;

    # Permissions policy — disable all browser APIs you do not need
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always;

    # Tell Tor Browser this is an onion service
    # Tor Browser uses this header for Onion-Location redirects
    # (only needed if you also have a clearnet site)
    # add_header Onion-Location http://your-address.onion$request_uri always;

    # === Logging Configuration ===

    # For an onion service, access logging is minimal by design
    # Tor strips client IP information — Nginx sees 127.0.0.1 for all requests
    # But the request paths and timestamps are still logged by default

    # Option 1: Disable access logging entirely (maximum privacy)
    access_log off;

    # Option 2: Log requests without timing information
    # log_format onion_minimal '$request_method $uri $status $body_bytes_sent';
    # access_log /var/log/nginx/onion-access.log onion_minimal;

    # Error logging — keep this for debugging, but at a reduced level
    error_log /var/log/nginx/onion-error.log warn;

    # === Security Configuration ===

    # Disable server version disclosure
    server_tokens off;

    # Limit request body size
    client_max_body_size 10M;

    # Timeouts — Tor connections are slower than clearnet
    # Set generous timeouts to account for circuit latency
    client_body_timeout 120s;
    client_header_timeout 120s;
    send_timeout 120s;
    proxy_read_timeout 120s;

    # Rate limiting — even on .onion, protect against abuse
    # Note: all clients appear as 127.0.0.1, so IP-based rate limiting
    # does not work. Use connection-level limits instead.
    limit_conn_zone $binary_remote_addr zone=onion_conn:1m;
    limit_conn onion_conn 20;

    location / {
        try_files $uri $uri/ =404;
    }

    # Block common attack paths
    location ~ /\.(git|env|htaccess|htpasswd) {
        deny all;
        return 404;
    }

    # Disable methods you do not need
    if ($request_method !~ ^(GET|HEAD|POST)$) {
        return 405;
    }
}
# Enable the site and test the configuration
sudo ln -s /etc/nginx/sites-available/onion-service /etc/nginx/sites-enabled/
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

sudo systemctl restart nginx

# Create a test page
sudo mkdir -p /var/www/onion
echo 'Onion Service Active

It works.

' \ | sudo tee /var/www/onion/index.html # Test locally curl -s http://127.0.0.1:8080/ # <!DOCTYPE html><html>...<h1>It works.</h1>...

The critical detail in this configuration: Nginx listens on 127.0.0.1:8080 only. Not on 0.0.0.0, not on the server's public IP. If you bind to 0.0.0.0, your web server is accessible on the clearnet through the public IP — completely defeating the purpose of running an onion service. This is the most common deployment mistake.

Running Multiple Onion Services on One Server

A single server can host multiple independent onion services, each with its own .onion address and keys. This is useful for separating different applications — a public website on one address, an admin panel on another, an API on a third — with cryptographic isolation between them:

# /etc/tor/torrc — Multiple onion services

# Service 1: Public website
HiddenServiceDir /var/lib/tor/website/
HiddenServicePort 80 127.0.0.1:8080

# Service 2: Admin panel (different .onion address)
HiddenServiceDir /var/lib/tor/admin/
HiddenServicePort 80 127.0.0.1:8081

# Service 3: API service
HiddenServiceDir /var/lib/tor/api/
HiddenServicePort 80 127.0.0.1:8082
HiddenServicePort 443 127.0.0.1:8443

# Service 4: SSH over onion (remote management without exposing SSH port)
HiddenServiceDir /var/lib/tor/ssh-access/
HiddenServicePort 22 127.0.0.1:22

That last entry is particularly powerful: SSH over Tor. If you configure SSH to listen only on 127.0.0.1 and access it exclusively through a .onion address, your server's SSH port is invisible on the clearnet. No port scanning, no brute-force attempts from the public internet, no exposure at all. The only way to reach SSH is through the Tor network with the correct .onion address.

# /etc/ssh/sshd_config — Lock SSH to localhost (onion-only access)
# WARNING: Make sure your Tor onion service is working BEFORE doing this
# or you will lock yourself out!

# Only listen on localhost — Tor routes connections here
ListenAddress 127.0.0.1

# Disable password authentication (key-only)
PasswordAuthentication no
PubkeyAuthentication yes

# Restrict to specific users
AllowUsers admin

# Client-side SSH config (~/.ssh/config) to connect:
# Host my-server-onion
#     HostName your-ssh-onion-address.onion
#     User admin
#     ProxyCommand nc -x 127.0.0.1:9050 -X 5 %h %p
#     # Or with torsocks:
#     # ProxyCommand torsocks nc %h %p

Vanity Onion Addresses: Practical Considerations

The default v3 onion address is a 56-character random string — functional but impossible for humans to remember or verify visually. Vanity addresses solve this by generating keys until the address starts with a desired prefix. For example, swisslyr... or private... as the first characters.

The tool for generating vanity v3 addresses is mkp224o, and the computational cost grows exponentially with prefix length:

# Install mkp224o
sudo apt install -y build-essential libsodium-dev autoconf
git clone https://github.com/cathugger/mkp224o.git
cd mkp224o
./autogen.sh
./configure
make

# Generate a vanity address
# 4-character prefix: seconds to minutes
# 5-character prefix: minutes to hours
# 6-character prefix: hours to days
# 7-character prefix: days to weeks
# 8+ character prefix: impractical on a single machine

# Example: generate addresses starting with "swiss"
./mkp224o -d /tmp/vanity-keys swiss

# This will create directories in /tmp/vanity-keys/ with the format:
# /tmp/vanity-keys/swiss[rest-of-address].onion/
#   ├── hostname
#   ├── hs_ed25519_public_key
#   └── hs_ed25519_secret_key

# To use a vanity address:
sudo systemctl stop tor
sudo cp -r /tmp/vanity-keys/swissXXXX.onion/* /var/lib/tor/my-onion-service/
sudo chown -R debian-tor:debian-tor /var/lib/tor/my-onion-service/
sudo chmod 700 /var/lib/tor/my-onion-service/
sudo chmod 600 /var/lib/tor/my-onion-service/*
sudo systemctl start tor

# IMPORTANT: After copying, securely delete the generation directory
# shred -u /tmp/vanity-keys/*/hs_ed25519_secret_key
# rm -rf /tmp/vanity-keys/

A practical note on vanity addresses: they are useful for branding and user trust (people can visually verify they are connecting to a known prefix), but they do not provide additional security. The cryptographic strength of the address is in the full 56 characters, not the prefix. A 5-character vanity prefix does not weaken the address — but it also does not strengthen it beyond what the full random address already provides.

Client Authentication: Restricting Access to Your Onion Service

By default, anyone who knows your .onion address can connect to it. For services that should be private — admin panels, internal tools, personal infrastructure — Tor provides built-in client authentication. Only clients with the correct key pair can connect; everyone else gets a connection refusal at the Tor protocol level, before reaching your web server:

# === Server-side: Configure authorised clients ===

# Create the authorised clients directory
sudo mkdir -p /var/lib/tor/my-onion-service/authorized_clients/

# Generate a key pair for each authorised client
# Client-side key generation (the client does this):
# openssl genpkey -algorithm x25519 -out /tmp/client-key.pem
# But for v3 onion auth, we use a specific format:

# Method: Generate on the client, share the public part with the server

# Client generates their key pair:
# (Run this on the CLIENT machine, not the server)
# openssl genpkey -algorithm x25519 -out private.pem
# openssl pkey -in private.pem -pubout -out public.pem

# For Tor onion client auth specifically, use this Python script:
python3 -c "
import base64
import os

# Generate 32-byte x25519 private key
private_key = os.urandom(32)
# In real deployment, derive public key properly
# This is simplified — use the tor client auth tools

priv_b32 = base64.b32encode(private_key).decode().rstrip('=')
print(f'Private key (give to client): {priv_b32}')
print(f'Save this in the client Tor Browser:')
print(f'  Settings > Privacy > Onion Services > Saved Keys')
"

# Or use the dedicated tool:
# Install tor client auth tools
sudo apt install -y basez

# Generate a proper x25519 keypair for Tor onion auth
openssl genpkey -algorithm x25519 -out /tmp/k1.prv.pem 2>/dev/null

# Extract raw private key (for the client)
CLIENT_PRIV=$(openssl pkey -in /tmp/k1.prv.pem -raw -outform der 2>/dev/null \
  | base32 | tr -d '=')

# Extract raw public key (for the server)  
CLIENT_PUB=$(openssl pkey -in /tmp/k1.prv.pem -raw -pubout -outform der 2>/dev/null \
  | base32 | tr -d '=')

# Create the server-side auth file
# Format: descriptor:x25519:BASE32_PUBLIC_KEY
echo "descriptor:x25519:${CLIENT_PUB}" \
  | sudo tee /var/lib/tor/my-onion-service/authorized_clients/client1.auth

# Set permissions
sudo chown debian-tor:debian-tor /var/lib/tor/my-onion-service/authorized_clients/client1.auth
sudo chmod 600 /var/lib/tor/my-onion-service/authorized_clients/client1.auth

# Restart Tor
sudo systemctl restart tor

# Give the client their private key (securely!)
echo "Client auth private key: ${CLIENT_PRIV}"
echo "The client adds this in Tor Browser under:"
echo "  Preferences > Privacy & Security > Onion Services"

# Clean up
shred -u /tmp/k1.prv.pem

With client authentication enabled, your onion service becomes truly private. It is not just unlisted — it is cryptographically inaccessible to anyone without the client key. Even if someone discovers the .onion address (through a leak, a log, or a misconfigured referrer header), they cannot connect.

Dual-Stack Configuration: Clearnet and Onion Together

Many operators want to serve their website on both the clearnet (via a normal domain) and as an onion service simultaneously. This lets regular users access the site normally while providing Tor users with a native .onion experience. The Onion-Location header tells Tor Browser that an onion version exists:

# /etc/nginx/sites-available/dual-stack
# Serve the same content on clearnet and .onion

# Clearnet site (regular HTTPS)
server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/html;
    index index.html;

    # Tell Tor Browser about the onion version
    # Tor Browser will show a purple ".onion available" badge
    # and offer to redirect automatically
    add_header Onion-Location http://your-address.onion$request_uri always;

    # Standard security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location / {
        try_files $uri $uri/ =404;
    }
}

# Onion service (HTTP only — .onion is already encrypted end-to-end)
server {
    listen 127.0.0.1:8080;
    server_name your-address.onion;

    root /var/www/html;  # Same content as clearnet
    index index.html;

    # Stricter headers for onion version
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "no-referrer" always;

    access_log off;
    server_tokens off;

    location / {
        try_files $uri $uri/ =404;
    }
}

The Onion-Location header is a standard that Tor Browser recognises. When a Tor Browser user visits your clearnet site, the browser sees the header and displays a notification: ".onion available." Clicking it redirects to the onion version, giving the user a fully anonymous connection without them needing to know or type the .onion address manually.

Operational Security: What Most Tutorials Skip

Deploying an onion service is the easy part. Keeping it anonymous is harder. The Tor protocol protects the network layer, but operational mistakes can leak your server's identity through other channels. Here is what to watch for:

1. Server clock correlation. If your server's NTP synchronisation pattern is unique (unusual drift, specific NTP server choices), it could theoretically be correlated with Tor traffic patterns. Use a common NTP pool and ensure your system clock stays accurate:

# Use standard NTP pools — do not point to a unique/private NTP server
# /etc/systemd/timesyncd.conf
[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org
FallbackNTP=ntp.ubuntu.com

2. DNS leaks from your server. If your server makes DNS queries from its public IP (for software updates, package downloads, API calls), those queries can be correlated with the onion service. Route all DNS through Tor or use a privacy-respecting DNS resolver:

# Option 1: Route DNS through Tor (strongest)
# Install torsocks
sudo apt install -y torsocks

# For one-off commands:
torsocks curl https://check.torproject.org

# Option 2: Use encrypted DNS (DoT/DoH)
# Install stubby for DNS-over-TLS
sudo apt install -y stubby

# /etc/stubby/stubby.yml (excerpt)
# resolution_type: GETDNS_RESOLUTION_STUB
# dns_transport_list:
#   - GETDNS_TRANSPORT_TLS
# upstream_recursive_servers:
#   - address_data: 185.95.218.42     # dns.digitale-gesellschaft.ch (Swiss)
#     tls_auth_name: "dns.digitale-gesellschaft.ch"
#   - address_data: 185.95.218.43
#     tls_auth_name: "dns.digitale-gesellschaft.ch"

# Point system resolver to stubby
# /etc/resolv.conf
# nameserver 127.0.0.1

3. Clearnet traffic patterns. If your onion service makes outbound clearnet connections (fetching remote images, calling external APIs, checking for updates), those connections originate from your server's real IP. An adversary correlating the timing of outbound connections with Tor circuit activity could identify your server:

# Audit outbound connections from your server
# Run this periodically to identify unexpected clearnet traffic

# Show all established outbound connections
ss -tnp | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort -u

# Monitor in real-time
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & tcp-syn != 0 and not src host 127.0.0.1' \
  -l 2>/dev/null | awk '{print $5}' | cut -d. -f1-4

# For high-security setups, route ALL outbound traffic through Tor:
# This requires transparent proxy configuration with iptables

# WARNING: This is an advanced configuration. Test thoroughly.
# Route all TCP traffic through Tor (transparent proxy mode)
# /etc/tor/torrc additions:
# VirtualAddrNetworkIPv4 10.192.0.0/10
# AutomapHostsOnResolve 1
# TransPort 9040 IsolateClientAddr IsolateClientProtocol
# DNSPort 5353

4. Web server fingerprinting. Default server headers, error pages, and response characteristics can fingerprint your web server software and version. If the same fingerprint appears on a clearnet site hosted at the same IP, the onion service's anonymity is compromised:

# /etc/nginx/nginx.conf — Minimise fingerprinting
http {
    # Remove version from headers
    server_tokens off;

    # Remove the Server header entirely (requires headers-more module)
    # sudo apt install libnginx-mod-http-headers-more-filter
    more_clear_headers Server;
    more_clear_headers X-Powered-By;

    # Custom error pages that do not reveal server software
    error_page 404 /custom-404.html;
    error_page 500 502 503 504 /custom-50x.html;

    # Disable ETag (can fingerprint file modification patterns)
    etag off;

    # Normalise response headers
    add_header X-Content-Type-Options "nosniff" always;
}

5. Application-level leaks. Your application might embed clearnet URLs, load fonts from Google, include analytics scripts, or reference resources from CDNs. Every external resource loaded from the onion version creates a connection from the client to a clearnet server — defeating the purpose of using Tor. The Content-Security-Policy header from our earlier Nginx configuration prevents this, but audit your HTML carefully:

# Check your HTML for external resource references
# Run against your site's HTML files
grep -rn 'https\?://' /var/www/onion/ \
  | grep -v 'your-address.onion' \
  | grep -v '^Binary'

# Common leaks to look for:
# - Google Fonts (fonts.googleapis.com)
# - CDN-hosted JavaScript (cdnjs.cloudflare.com, unpkg.com)
# - Analytics (google-analytics.com, plausible.io)
# - Embedded videos (youtube.com, vimeo.com)
# - Social media widgets (platform.twitter.com, facebook.com)
# - Font Awesome from CDN

# ALL external resources must be self-hosted for the onion version
# Download and serve them locally

Monitoring Your Onion Service Health

Monitoring an onion service requires different tools than clearnet monitoring. You cannot use standard uptime checkers (they do not support .onion addresses), and you need to test the full Tor circuit — not just the web server.

#!/bin/bash
# /usr/local/bin/onion-health-check.sh
# Monitor onion service availability and performance
# Run via cron every 15 minutes

ONION_ADDRESS="your-address.onion"
LOG_FILE="/var/log/onion-health.log"
ALERT_EMAIL=""  # Optional

# Check 1: Tor service running
if ! systemctl is-active --quiet tor; then
    echo "$(date -Iseconds) CRITICAL: Tor service not running" >> "$LOG_FILE"
    systemctl restart tor
    echo "$(date -Iseconds) ACTION: Tor service restarted" >> "$LOG_FILE"
    exit 1
fi

# Check 2: Onion service descriptor is published
TOR_LOG="/var/log/tor/notices.log"
LAST_PUBLISH=$(grep "Successfully uploaded" "$TOR_LOG" 2>/dev/null | tail -1)
if [ -z "$LAST_PUBLISH" ]; then
    echo "$(date -Iseconds) WARNING: No descriptor upload found in Tor log" >> "$LOG_FILE"
fi

# Check 3: Local web server responding
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ 2>/dev/null)
if [ "$HTTP_CODE" != "200" ]; then
    echo "$(date -Iseconds) CRITICAL: Local web server returned $HTTP_CODE" >> "$LOG_FILE"
    exit 1
fi

# Check 4: Full circuit test (requires torsocks)
if command -v torsocks &> /dev/null; then
    START=$(date +%s%N)
    ONION_CODE=$(torsocks curl -s -o /dev/null -w '%{http_code}' \
        --max-time 60 "http://${ONION_ADDRESS}/" 2>/dev/null)
    END=$(date +%s%N)
    LATENCY_MS=$(( (END - START) / 1000000 ))
    
    if [ "$ONION_CODE" = "200" ]; then
        echo "$(date -Iseconds) OK: Onion service reachable (${LATENCY_MS}ms)" >> "$LOG_FILE"
    else
        echo "$(date -Iseconds) WARNING: Onion circuit test returned $ONION_CODE (${LATENCY_MS}ms)" >> "$LOG_FILE"
    fi
fi

# Check 5: Tor circuit health
CIRCUIT_COUNT=$(sudo -u debian-tor tor --list-fingerprint 2>/dev/null | wc -l)
echo "$(date -Iseconds) INFO: Active circuits: approximately ${CIRCUIT_COUNT}" >> "$LOG_FILE"
# Add to cron for regular monitoring
echo "*/15 * * * * root /usr/local/bin/onion-health-check.sh" \
  | sudo tee /etc/cron.d/onion-health

Backup and Key Management

Your onion service's identity is defined by the ED25519 key pair in the HiddenServiceDir. Losing these keys means losing the .onion address permanently — there is no recovery mechanism, no registrar to contact, no reset process. Key management is critical:

#!/bin/bash
# /usr/local/bin/onion-backup.sh
# Encrypted backup of onion service keys
# Run after initial setup and periodically

BACKUP_DIR="/root/onion-backups"
DATE=$(date +%Y-%m-%d)
SERVICE_DIR="/var/lib/tor/my-onion-service"

mkdir -p "$BACKUP_DIR"

# Create a tarball of the service keys
sudo tar czf "/tmp/onion-keys-${DATE}.tar.gz" \
    -C /var/lib/tor \
    my-onion-service/hostname \
    my-onion-service/hs_ed25519_public_key \
    my-onion-service/hs_ed25519_secret_key

# Encrypt with a strong passphrase
# (GPG symmetric encryption — no key infrastructure needed)
gpg --symmetric --cipher-algo AES256 \
    --output "${BACKUP_DIR}/onion-keys-${DATE}.tar.gz.gpg" \
    "/tmp/onion-keys-${DATE}.tar.gz"

# Securely delete the unencrypted tarball
shred -u "/tmp/onion-keys-${DATE}.tar.gz"

# Verify the backup
gpg --list-packets "${BACKUP_DIR}/onion-keys-${DATE}.tar.gz.gpg" | head -5

echo "Backup saved to: ${BACKUP_DIR}/onion-keys-${DATE}.tar.gz.gpg"
echo "Store this file OFFLINE and SEPARATE from your server"
echo "The passphrase is NOT stored anywhere on this server"

# Retention: keep last 3 backups
ls -t "${BACKUP_DIR}"/onion-keys-*.gpg | tail -n +4 | xargs -r shred -u

Store the encrypted backup on a separate physical medium — a USB drive in a safe, an air-gapped machine, an encrypted cloud storage service accessed through Tor. Do not store onion service key backups on the same server or in the same data centre. If the server is seized or compromised, you need the backup to be elsewhere.

Performance Tuning for Onion Services

Onion services are inherently slower than clearnet connections. Every request traverses six Tor relays (three on the client side, three on the server side) plus a rendezvous point. Typical latency is 500ms–2s for the initial connection and 200ms–800ms per subsequent request. You cannot eliminate this latency, but you can minimise what your server adds on top of it:

# /etc/nginx/nginx.conf — Performance tuning for onion services

http {
    # Enable gzip compression — reduces bytes over slow Tor circuits
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript
               image/svg+xml;
    gzip_min_length 256;

    # Enable sendfile for static content
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keep-alive connections — critical for Tor performance
    # Each new TCP connection through Tor adds 1-3 seconds
    # Keep connections alive to reuse circuits
    keepalive_timeout 120;
    keepalive_requests 1000;

    # Buffer sizes
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # Static asset caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

# In your torrc — increase connection handling capacity
# /etc/tor/torrc additions for busy onion services:

# Allow more simultaneous rendezvous circuits
# Default is fine for low-traffic services
# For higher traffic, increase available resources:
# HiddenServiceMaxStreams 100
# HiddenServiceMaxStreamsCloseCircuit 1

The single most impactful optimisation is minimising page weight. A clearnet page that loads 3 MB of JavaScript, web fonts, and hero images is merely slow. The same page over Tor is unusable. Aim for pages under 500 KB — ideally under 200 KB. Inline critical CSS, defer non-essential JavaScript, compress images aggressively, and self-host everything.

Why Swiss Jurisdiction Matters for Onion Services

Running onion services is legal in Switzerland and in most other jurisdictions. Tor itself is a legitimate privacy tool used by journalists, activists, whistleblowers, businesses protecting trade secrets, and privacy-conscious individuals. The Tor Project is a US-based nonprofit, and operating Tor software — including onion services — is protected activity.

However, the legal and jurisdictional environment where your server physically sits affects the risks you face as an operator. This is where hosting on a Swiss VPS or Swiss dedicated server provides concrete advantages:

No blanket data retention. Switzerland does not impose the kind of mandatory data retention requirements that exist in some EU member states. Your hosting provider is not compelled to log your server's network connections or Tor traffic metadata. What you do not retain cannot be seized.

Strong mutual legal assistance requirements. Foreign governments cannot simply subpoena your server. Requests must go through the Swiss Federal Office of Justice and meet the dual criminality standard — the activity must be a crime in both the requesting country and Switzerland. This filters out many overreaching requests.

FADP privacy protections. The Swiss Federal Act on Data Protection provides a legal framework that supports the privacy principles inherent in running onion services. Data minimisation, purpose limitation, and proportionality are legal requirements — not optional best practices.

Political stability and neutrality. Switzerland's long tradition of political neutrality means your infrastructure is not subject to the geopolitical pressures that affect servers in countries actively engaged in surveillance agreements like Five Eyes, Nine Eyes, or Fourteen Eyes. Swiss data centres are outside these intelligence-sharing arrangements.

This does not make Swiss hosting a magic shield. If you use an onion service for illegal activity, Swiss law enforcement can and will investigate. The point is that legitimate privacy infrastructure — protecting journalistic sources, securing business communications, providing anonymous access to lawful content — operates in a legal environment that respects and supports these uses rather than treating them as inherently suspicious.

Common Deployment Mistakes

After deploying onion services on production infrastructure for several years, these are the mistakes we see most often:

• Binding Nginx to 0.0.0.0 instead of 127.0.0.1. This exposes your web server on the clearnet, correlating the onion service with a public IP address. Always use listen 127.0.0.1:PORT for onion-only services
• Loading external resources. A single <link href="https://fonts.googleapis.com/..."> in your HTML causes the client's browser to make a clearnet connection to Google. Self-host all resources
• Running a Tor relay on the same server. This provides a plausible reason for Tor traffic but makes traffic analysis easier — the relay's public IP is known, and correlating onion service activity with relay activity is feasible
• Forgetting to backup keys. One disk failure, one compromised server, and your .onion address is gone forever. The address is the identity — there is no recovery
• Using HTTP for onion-to-clearnet proxying. If your onion service proxies requests to a clearnet backend, that backend connection is not protected by Tor. Use HTTPS for all backend connections that leave the server
• Ignoring Tor log warnings. The Tor log at /var/log/tor/notices.log contains important warnings about clock skew, circuit failures, and configuration issues. Monitor it
• Default error pages. Nginx and Apache default error pages reveal the server software and version. Customise them

Hardening Checklist for Production Onion Services

Before considering your onion service production-ready, verify each of these:

#!/bin/bash
# /usr/local/bin/onion-security-audit.sh
# Pre-production security checklist for onion services

echo "=== Onion Service Security Audit ==="
echo ""

# 1. Nginx binding check
echo "[1] Nginx listen directives:"
grep -rn "listen " /etc/nginx/sites-enabled/ | grep -v "127.0.0.1"
if [ $? -eq 0 ]; then
    echo "  WARNING: Nginx is listening on non-localhost addresses!"
else
    echo "  OK: All listen directives are localhost-only"
fi
echo ""

# 2. Key permissions
echo "[2] Onion service key permissions:"
ls -la /var/lib/tor/*/hs_ed25519_secret_key 2>/dev/null
PERMS=$(stat -c %a /var/lib/tor/*/hs_ed25519_secret_key 2>/dev/null | head -1)
if [ "$PERMS" = "600" ]; then
    echo "  OK: Key permissions are 600"
else
    echo "  WARNING: Key permissions should be 600, found $PERMS"
fi
echo ""

# 3. External resource check
echo "[3] External resources in web content:"
EXTERNAL=$(grep -rn 'https\?://' /var/www/onion/ 2>/dev/null \
  | grep -v '.onion' | grep -v 'Binary' | wc -l)
if [ "$EXTERNAL" -gt 0 ]; then
    echo "  WARNING: $EXTERNAL lines reference external resources"
    grep -rn 'https\?://' /var/www/onion/ | grep -v '.onion' | grep -v 'Binary' | head -5
else
    echo "  OK: No external resource references found"
fi
echo ""

# 4. Server header disclosure
echo "[4] Server header check:"
HEADERS=$(curl -s -I http://127.0.0.1:8080/ 2>/dev/null | grep -i "^server:")
if [ -n "$HEADERS" ]; then
    echo "  WARNING: Server header exposed: $HEADERS"
else
    echo "  OK: No server header disclosed"
fi
echo ""

# 5. Tor relay check
echo "[5] Tor relay status:"
OR_PORT=$(grep "^ORPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
if [ "$OR_PORT" = "0" ] || [ -z "$OR_PORT" ]; then
    echo "  OK: Not running as a Tor relay"
else
    echo "  WARNING: ORPort is set to $OR_PORT — this server is a Tor relay"
fi
echo ""

# 6. Open ports check
echo "[6] Externally accessible ports:"
ss -tlnp | grep -v "127.0.0.1" | grep -v "::1"
echo ""

# 7. Backup check
echo "[7] Key backup status:"
BACKUP_COUNT=$(ls /root/onion-backups/*.gpg 2>/dev/null | wc -l)
if [ "$BACKUP_COUNT" -gt 0 ]; then
    echo "  OK: $BACKUP_COUNT encrypted backup(s) found"
    ls -la /root/onion-backups/*.gpg 2>/dev/null | tail -3
else
    echo "  WARNING: No encrypted key backups found in /root/onion-backups/"
fi
echo ""

# 8. Tor version
echo "[8] Tor version:"
tor --version 2>/dev/null | head -1
echo ""

echo "=== Audit Complete ==="

The Honest Assessment

Onion services are not a universal solution. They add complexity, latency, and operational overhead. The .onion addresses are unwieldy (56 characters of base32 is not something anyone types from memory). The Tor network's bandwidth is limited — if you are serving large files or high-traffic applications, the performance will be noticeably worse than clearnet hosting. Client support beyond Tor Browser is inconsistent.

But for their intended use cases — protecting the identity of your server, providing anonymous access to services, offering a censorship-resistant endpoint, or securing internal infrastructure behind cryptographic authentication — onion services are unmatched. There is no clearnet equivalent that provides the same combination of server anonymity, mutual authentication without certificate authorities, and end-to-end encryption without trust in intermediaries.

If you are already running on Swiss infrastructure because jurisdiction matters to your threat model, adding an onion service is a natural next step. The Swiss FADP protects your server-side data. Tor protects the network layer. Together, they create an infrastructure stack where privacy is not a feature you bolt on — it is the architecture.

Your server. Your keys. Your .onion address. No registrar, no certificate authority, no DNS provider, and no upstream network operator standing between you and your users. That is what privacy hosting in Switzerland looks like when you take it seriously.