Swiss...
WireGuard VPN on Swiss Infrastructure: Privacy-First Deployment Guide
From manual WireGuard setup to wg-easy Docker UI to full logging elimination — build a VPN that actually respects privacy.
August 17, 2026
by SwissLayer 18 min read
wireguard-vpn-swiss-infrastructure

Commercial VPN providers make big promises. No logs. Military-grade encryption. Complete anonymity. Then a court order lands and suddenly logs appear that were never supposed to exist. PureVPN handed connection logs to the FBI in 2017. IPVanish did the same. HideMyAss gave up a LulzSec member. The list keeps growing.

The problem is not the VPN protocol. The problem is trust. When you route your traffic through someone else's infrastructure, you are replacing your ISP's ability to monitor you with a VPN company's ability to monitor you. You have shifted the surveillance point, not eliminated it.

The only VPN you can trust is the one you operate yourself, on infrastructure you control, in a jurisdiction that respects privacy by law — not by marketing copy.

This guide covers three things: deploying WireGuard manually on a Swiss VPS, simplifying management with wg-easy's Docker-based web UI, and then doing what commercial VPNs claim but rarely deliver — eliminating every log on the system so there is genuinely nothing to hand over.

Why WireGuard Over OpenVPN or IPsec

WireGuard is roughly 4,000 lines of code. OpenVPN is over 100,000. IPsec implementations in the Linux kernel span hundreds of thousands of lines across multiple subsystems. This is not a trivia point — it is a security argument.

Every line of code is a potential vulnerability. Smaller codebases are easier to audit, easier to reason about, and have fewer places for bugs to hide. WireGuard's cryptographic choices are fixed — ChaCha20 for symmetric encryption, Poly1305 for authentication, Curve25519 for key exchange, BLAKE2s for hashing. No cipher negotiation, no configuration matrix, no downgrade attacks. It either uses modern cryptography or it does not connect.

Performance is the other advantage. WireGuard runs inside the Linux kernel as a network interface. There is no userspace daemon shuffling packets through TUN/TAP devices. On identical hardware, WireGuard consistently delivers 2-4x the throughput of OpenVPN and lower latency than most IPsec configurations. On a Swiss VPS with a 1Gbps port, you will actually use that bandwidth instead of bottlenecking at 300-400Mbps on OpenVPN.

The simplicity extends to configuration. A complete WireGuard setup is a single config file per peer. No certificate authority, no PKI infrastructure, no renewal scripts. Generate a key pair, exchange public keys, define allowed IPs. That is it.

Manual WireGuard Setup on a Swiss VPS

Start with a clean Ubuntu 22.04 or Debian 12 installation on your Swiss VPS. WireGuard is included in the default kernel since Linux 5.6, so installation is straightforward.

Install WireGuard tools:

apt update && apt install -y wireguard wireguard-tools

Generate server keys:

# Generate private and public keys
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

Create the server configuration:

# /etc/wireguard/wg0.conf

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <contents of server_private.key>

# NAT and forwarding rules
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Client 1
[Peer]
PublicKey = <client1_public_key>
AllowedIPs = 10.0.0.2/32

# Client 2
[Peer]
PublicKey = <client2_public_key>
AllowedIPs = 10.0.0.3/32

Replace eth0 with your actual network interface name. Check with ip route show default — it is often ens3, ens18, or enp0s3 on virtual machines.

Enable IP forwarding:

# Enable immediately
sysctl -w net.ipv4.ip_forward=1

# Make persistent across reboots
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf

Start WireGuard and enable on boot:

systemctl enable --now wg-quick@wg0

# Verify it is running
wg show

You should see the interface with its public key, listening port, and any connected peers.

Client Configuration

On each client device (laptop, phone, router), generate a key pair and create a config file:

# Generate client keys (on the client machine)
wg genkey | tee client_private.key | wg pubkey > client_public.key

Create the client configuration file:

# /etc/wireguard/wg0.conf (on client)

[Interface]
PrivateKey = <client_private_key>
Address = 10.0.0.2/24
DNS = 1.1.1.1, 9.9.9.9

[Peer]
PublicKey = <server_public_key>
Endpoint = your-swiss-vps-ip:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Setting AllowedIPs = 0.0.0.0/0, ::/0 routes all traffic through the VPN. For split tunneling — where only specific subnets go through the VPN — replace this with the specific CIDR ranges you want to route.

Adding the client's public key to the server can be done live without restarting WireGuard:

# On the server — add a peer without restarting
wg set wg0 peer <client_public_key> allowed-ips 10.0.0.2/32

DNS Leak Prevention and Kill Switch

A VPN is useless if DNS queries leak outside the tunnel. Two things to lock down:

DNS leak prevention: The DNS = 1.1.1.1, 9.9.9.9 line in the client config tells the WireGuard client to use those DNS servers through the tunnel. On Linux clients, verify with:

# Check for DNS leaks
resolvectl status
# Should show wg0 using 1.1.1.1 / 9.9.9.9, not your ISP's DNS

Kill switch via iptables: If the VPN tunnel drops, traffic should stop entirely — not fall back to your unprotected connection. Add these rules on the client:

# Kill switch — block all traffic unless it goes through WireGuard
iptables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
ip6tables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT

# Allow traffic to the WireGuard endpoint itself (so the tunnel can reconnect)
iptables -I OUTPUT -d your-swiss-vps-ip -p udp --dport 51820 -j ACCEPT

This ensures that if wg0 goes down, no traffic leaves the machine unencrypted. The connection simply stops until the tunnel comes back up.

wg-easy: WireGuard with a Web UI via Docker

Manual WireGuard configuration works, but managing multiple clients — generating keys, distributing configs, tracking who is connected — gets tedious fast. This is where wg-easy changes the game.

wg-easy is an open-source project that packages WireGuard with a clean, modern web-based administration interface inside a single Docker container. It handles everything: key generation, client management, QR codes for mobile devices, traffic statistics, enable/disable toggles per client, and more.

Features that make it worth using:

QR code generation — scan with the WireGuard mobile app, connected in seconds
Client management — add, remove, enable, disable clients from the web UI
Traffic statistics — per-client Tx/Rx counters and bandwidth charts
Two-factor authentication (2FA) — TOTP-based login protection for the admin panel
OIDC support — integrate with your existing identity provider
One-time links — share client configs that expire after first use
Client expiration — set automatic expiry dates for temporary access
Per-client firewall filtering — restrict what each client can access through the VPN

Prerequisites: Install Docker and Docker Compose on your Swiss VPS if not already present:

curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

Generate a password hash for the web UI (bcrypt):

# Install apache2-utils for htpasswd, or use Docker
docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw 'YOUR_SECURE_PASSWORD'

# Output will look like: $2a$12$abc...xyz
# When pasting into docker-compose.yml, escape each $ as $$

Create the docker-compose.yml:

# /opt/wg-easy/docker-compose.yml

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    environment:
      - LANGUAGE=en
      - WG_HOST=your-server-ip-or-domain
      - PASSWORD_HASH=$$2a$$12$$hashed_password_here
      - WG_DEFAULT_DNS=1.1.1.1,9.9.9.9
      - WG_ALLOWED_IPS=0.0.0.0/0,::/0
      - UI_TRAFFIC_STATS=true
      - UI_CHART_TYPE=2
    volumes:
      - wg-easy:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

volumes:
  wg-easy:

Key environment variables explained:

WG_HOST — your server's public IP or domain name. Clients use this to connect
PASSWORD_HASH — bcrypt hash of your admin password. Double-escape dollar signs with $$ in YAML
WG_DEFAULT_DNS — DNS servers pushed to clients. Use privacy-respecting resolvers
WG_ALLOWED_IPS — CIDR ranges clients can access. 0.0.0.0/0,::/0 means route everything
UI_TRAFFIC_STATS — show per-client bandwidth usage in the web interface
UI_CHART_TYPE — bandwidth chart style (1 = line, 2 = area, 3 = bar)

Launch it:

cd /opt/wg-easy
docker compose up -d

Access the web UI: Open http://your-server-ip:51821 in a browser. Log in with the password you hashed. You will see a clean dashboard where you can add clients with one click.

Adding a client: Click "New Client," give it a name (e.g., "Phone," "Laptop," "Router"). wg-easy generates the key pair, creates the config, and displays a QR code. On your phone, open the WireGuard app, tap the + icon, select "Scan from QR code," point at the screen. Connected. The entire process takes about ten seconds.

Securing wg-easy with a Reverse Proxy

The web UI runs on HTTP by default. For production, put it behind an HTTPS reverse proxy. Here is a minimal Nginx configuration:

# /etc/nginx/sites-available/wg-easy

server {
    listen 443 ssl http2;
    server_name vpn.yourdomain.com;

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

    location / {
        proxy_pass http://127.0.0.1:51821;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support (for real-time stats)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

server {
    listen 80;
    server_name vpn.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

With the reverse proxy in place, change the wg-easy port binding to only listen on localhost: "127.0.0.1:51821:51821/tcp" in the docker-compose.yml. This prevents direct HTTP access to the admin panel from the internet.

Keeping wg-easy updated:

# Pull the latest image and restart
cd /opt/wg-easy
docker compose up --detach --pull always

Run this periodically or automate it with a cron job. wg-easy is actively maintained and receives regular security patches.

Disabling ALL Logging: Making Privacy Real

Here is where most "privacy VPN" guides stop. They show you how to set up WireGuard, maybe mention Docker, and call it done. But your VPN server is still running a full Linux operating system that logs everything by default — every SSH login, every service start, every kernel message, every Docker container output. If someone gains access to your server — through a legal order, a hosting provider's cooperation, or a compromise — those logs tell the entire story of who connected, when, and what they did.

If your VPS logs connections, your privacy is theater.

This section covers disabling every logging mechanism on a Linux VPS. This is an aggressive approach. It makes debugging significantly harder. The trade-off is intentional: you are choosing privacy over convenience.

Recommended approach: Keep logging enabled during initial setup and testing. Once everything is stable and verified working, come back to this section and disable it all.

Step 1: Disable systemd Journal Logging

systemd-journald is the default logging daemon on modern Linux distributions. It captures output from every service, kernel messages, and system events. Disable persistent storage:

# Edit journald configuration
cat > /etc/systemd/journald.conf << 'EOF'
[Journal]
Storage=none
Compress=no
Seal=no
ForwardToSyslog=no
ForwardToKMsg=no
ForwardToConsole=no
ForwardToWall=no
EOF

# Restart journald to apply
systemctl restart systemd-journald

# Clear any existing journal data
journalctl --rotate
journalctl --vacuum-time=1s

With Storage=none, journald keeps logs only in a volatile ring buffer in memory. They disappear on reboot and cannot grow beyond a small memory allocation. Combined with the ForwardTo*=no directives, nothing is forwarded to other logging systems either.

Step 2: Disable rsyslog

rsyslog is the traditional syslog daemon. Even with journald configured to not forward, rsyslog may still capture messages directly from the kernel or from applications that write to /dev/log:

# Stop and disable rsyslog
systemctl stop rsyslog
systemctl disable rsyslog

# Verify it is dead
systemctl status rsyslog

If your system uses syslog-ng instead (common on some Debian configurations):

systemctl stop syslog-ng
systemctl disable syslog-ng

Step 3: Disable Kernel Logging (dmesg)

The kernel ring buffer (accessible via dmesg) logs hardware events, driver messages, and network interface changes — including WireGuard interface bring-up events. Restrict access and reduce verbosity:

# Restrict dmesg access to root only
echo "kernel.dmesg_restrict = 1" >> /etc/sysctl.d/99-no-logs.conf

# Reduce kernel log verbosity to emergencies only
echo "kernel.printk = 0 0 0 0" >> /etc/sysctl.d/99-no-logs.conf

# Apply immediately
sysctl -p /etc/sysctl.d/99-no-logs.conf

This does not fully eliminate kernel messages but restricts who can read them and reduces what gets generated. The kernel ring buffer is volatile — it exists only in memory and is lost on reboot.

Step 4: Disable Login Records (wtmp, btmp, lastlog)

Linux tracks every login, logout, and failed authentication attempt in binary log files. These are the records that commands like last, lastb, and lastlog read from. On a privacy VPN server, these need to go:

# Truncate existing records
> /var/log/wtmp
> /var/log/btmp
> /var/log/lastlog

# Make them immutable — even root cannot write to them
# (without first removing the immutable flag)
chattr +i /var/log/wtmp /var/log/btmp /var/log/lastlog

# Verify
lsattr /var/log/wtmp /var/log/btmp /var/log/lastlog
# Should show: ----i---------- for each file

The chattr +i flag sets the immutable attribute at the filesystem level. The system will continue trying to write login records to these files but will silently fail. No errors, no crashes — just no records.

Step 5: Disable Docker Container Logging

By default, Docker captures everything that containers write to stdout and stderr and stores it as JSON files on disk. For a wg-easy container, this could include peer connection events and handshake logs. Disable Docker logging globally:

# Create or edit Docker daemon configuration
cat > /etc/docker/daemon.json << 'EOF'
{
  "log-driver": "none"
}
EOF

# Restart Docker to apply
systemctl restart docker

# Restart wg-easy so it picks up the new logging driver
cd /opt/wg-easy
docker compose up -d --force-recreate

With "log-driver": "none", Docker does not write any container output to disk. docker logs wg-easy will return nothing. This is exactly what you want.

If you want per-container control instead of a global setting, add the logging directive to your docker-compose.yml:

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    logging:
      driver: none
    # ... rest of config

Step 6: Confirm WireGuard Is Not Logging

WireGuard itself does not log by default — this is one of its design principles. There is no logging configuration option in the WireGuard kernel module. However, verify that no debug or verbose logging has been enabled:

# Check for any Log directives in WireGuard configs
grep -ri "log" /etc/wireguard/ 2>/dev/null

# For wg-easy, check the mounted volume
docker exec wg-easy grep -ri "log" /etc/wireguard/ 2>/dev/null

# Neither should return anything related to logging

WireGuard's lack of logging is not a missing feature — it is an intentional cryptographic design decision. The protocol does not even have a concept of "connection" or "session" in the traditional sense. Peers exchange encrypted UDP packets. There is no handshake logging, no connection state logging, no traffic logging. It is privacy by architecture.

Step 7: Disable Cron and At Logging

The cron daemon logs every job execution, including timestamps. If you are using cron for any maintenance tasks, these logs can reveal server activity patterns:

# For cron, edit /etc/default/cron (Debian/Ubuntu)
# Add EXTRA_OPTS to suppress logging
echo 'EXTRA_OPTS="-L 0"' >> /etc/default/cron
systemctl restart cron

# Disable at daemon if not needed
systemctl stop atd
systemctl disable atd

Step 8: Clean Existing Logs and Prevent New Ones

After disabling all logging daemons, clean up whatever logs already exist on the system:

# Remove all existing log files
find /var/log -type f -name "*.log" -delete
find /var/log -type f -name "*.gz" -delete
find /var/log -type f -name "*.old" -delete
find /var/log -type f -name "*.1" -delete

# Truncate log files that are held open by running processes
find /var/log -type f -exec truncate -s 0 {} \;

# Remove log rotation configs (no logs = no need to rotate)
rm -f /etc/logrotate.d/*

Step 9: Verify Nothing Is Logging

After all the above changes, verify that the system is genuinely not writing logs anywhere:

# Create a timestamp marker
touch /tmp/log-check-marker

# Wait a few minutes, then check for any new files in /var/log
sleep 180
find /var/log -type f -newer /tmp/log-check-marker -ls

# Check journald (should show nothing or minimal entries)
journalctl --no-pager 2>&1 | wc -l

# Check Docker logs (should return error or empty)
docker logs wg-easy 2>&1 | wc -l

# Check for any syslog sockets still active
ss -xlnp | grep -i log

# Check what is writing to /var/log
lsof +D /var/log 2>/dev/null

If find returns new files, investigate what is creating them. Common culprits include fail2ban (if installed), unattended-upgrades, or custom scripts. Disable or reconfigure each one individually.

The Honest Trade-Off

Let us be direct about what you are giving up. With all logging disabled:

Debugging is painful. When something breaks, you have no logs to diagnose it. You are flying blind. You will need to re-enable logging temporarily to troubleshoot, then disable it again once the issue is resolved.
Security monitoring is gone. You cannot detect brute-force SSH attempts, unusual process behavior, or unauthorized access without logs. Compensate with external monitoring and strict firewall rules.
Compliance frameworks may object. Some regulatory frameworks require logging. If you are subject to such requirements, this approach conflicts with them. Know your legal obligations.
You need to trust your setup. Without logs, you will not know if something quietly failed at 3 AM. Automated health checks that report externally (not to local logs) are essential.

This is a real trade-off, not a free win. You are optimizing for one thing — genuine privacy — at the cost of operational visibility. For a personal VPN server where privacy is the entire point, this trade-off makes sense. For production infrastructure serving customers, it probably does not.

Firewall Hardening for Your VPN Server

With logging disabled, your firewall becomes even more critical. Lock down the server to only the ports it needs:

# Reset iptables to a clean state
iptables -F
iptables -X

# Default policies: drop everything
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT

# Allow SSH (restrict to your management IP if possible)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Better: iptables -A INPUT -p tcp --dport 22 -s YOUR_MGMT_IP -j ACCEPT

# Allow WireGuard
iptables -A INPUT -p udp --dport 51820 -j ACCEPT

# Allow wg-easy web UI (only if NOT behind reverse proxy)
# iptables -A INPUT -p tcp --dport 51821 -j ACCEPT

# Allow forwarding through the VPN
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# NAT for VPN clients
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# Save rules
apt install -y iptables-persistent
netfilter-persistent save

If you restricted SSH to a specific management IP, make absolutely sure that IP is correct and static. With logging disabled, you will not see failed login attempts — but with a proper firewall, there should not be any to see.

Why Swiss Infrastructure for a Privacy VPN

Jurisdiction matters. Switzerland is not in the EU and not subject to EU data retention directives. The Swiss Federal Act on Data Protection (FADP) provides strong protections that are enforced domestically, not watered down by multinational agreements. Swiss authorities cannot compel a hosting provider to install monitoring equipment the way US National Security Letters or UK Investigatory Powers Act orders can.

This does not mean Switzerland is a lawless haven — it is not. Swiss courts can and do issue lawful orders for data access. But those orders require Swiss judicial oversight, probable cause for serious crimes, and cannot be issued secretly or extraterritorially. The bar is higher, the process is transparent, and the scope is narrower than most comparable jurisdictions.

For a self-hosted VPN, the jurisdiction of your server determines whose legal system governs access to that server. A VPN running on a Swiss VPS benefits from Swiss privacy law. A VPN running on an AWS instance in Frankfurt does not, regardless of what country you personally are in.

Infrastructure quality matters too. Swiss data centers operate on redundant power grids, maintain strict physical access controls, and connect to major European internet exchanges with low-latency peering. Your VPN is only useful if it is fast and reliable. A server that drops offline or routes traffic through congested paths defeats the purpose.

SwissLayer's dedicated servers provide bare-metal hardware — no shared hypervisor, no noisy neighbors, no cloud provider middleware between you and the network stack. For a WireGuard deployment, this means kernel-level performance with direct NIC access. Combined with unmetered bandwidth on 1Gbps+ ports, your VPN will not be the bottleneck.

Operational Checklist

Before you call this deployment complete, verify each item:

WireGuard connectivity: Connect from a client, verify IP shows as your Swiss VPS IP at ifconfig.me or ip.me
DNS leak test: Visit dnsleaktest.com and run the extended test. Only your configured DNS servers should appear
WebRTC leak test: Visit browserleaks.com/webrtc. Your real IP should not be visible
Kill switch verification: Disconnect WireGuard. Try to access any website. Nothing should load
wg-easy dashboard: Confirm all clients appear, traffic stats are updating, QR codes generate correctly
Logging verification: Run the verification commands from Step 9. Confirm zero log output
Firewall verification: nmap your-server-ip from an external host. Only ports 22 (SSH), 51820/udp (WireGuard), and optionally 443 (if using reverse proxy) should be open
Reboot persistence: Reboot the server. Verify WireGuard, Docker, wg-easy, and firewall rules all come back automatically

Run through this checklist after every change. A VPN that leaks once is a VPN that was not worth building.

Closing Thoughts

A self-hosted WireGuard VPN on Swiss infrastructure is not the easiest path. A commercial VPN subscription is simpler — download an app, click connect, done. But simplicity is not the point. Control is the point. Verifiability is the point.

With a commercial VPN, you trust a company's no-logs claim. With your own server, you can verify it. You can read the WireGuard source code, inspect the kernel module, audit the Docker container, check every log directory, and confirm with your own eyes that nothing is being recorded. That verification is something no commercial VPN can offer.

Swiss jurisdiction adds legal backing to what the technical setup provides. The combination of WireGuard's minimal, auditable codebase running on infrastructure protected by Swiss privacy law, with logging explicitly eliminated at every layer of the operating system, is as close to a truly private VPN as current technology allows.

It is not perfect — nothing is. But it is honest. And in the VPN space, honesty is rare.