Deploying **WG-EASY** with **Pangolin**

Deploying wg-easy with Pangolin

1. Prerequisites

  • A VPS (Ubuntu/Debian recommended) with Docker + Docker Compose installed.
  • A domain name (for Pangolin + wg-easy subdomains).
  • Firewall access to open required ports.
  • Pangolin already running (with Gerbil + Traefik stack).

2. Ports Overview

Service Default Port(s) Protocol Notes
Pangolin 80, 443, 51820 TCP/UDP 80/443 for Traefik HTTPS, 51820/UDP for Newt WireGuard tunnel
wg-easy 51820/UDP, 51821/TCP UDP/TCP 51820 is WireGuard VPN, 51821 is wg-easy web UI

Since Pangolin also uses 51820/UDP for its own WireGuard tunnels, you must remap wg-easy’s WireGuard port to avoid conflicts (e.g., 51830/udp in gerbil/traefik).


3. Docker Network Setup

To let Pangolin and wg-easy talk without exposing raw ports, attach them to a shared Docker network.

which is `pangolin` network

4. Example docker-compose.yml

Here’s a minimal stack for wg-easy alongside Pangolin:

volumes:
  etc_wireguard:

networks:
  pangolin:
    external: true   # use Pangolin's existing network

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - WG_HOST=vpn.development.hhf.technology
      # Run this in the container to get which interface: `ip route get 8.8.8.8 | awk '{print $5}'`
      - WG_DEVICE=eth0
      - WG_PORT=51830
      - PORT=51821
      - DISABLE_IPV6=true
      - WG_ALLOWED_IPS=0.0.0.0/0, 1.1.1.1/32
      - LANG=en
      - WG_PERSISTENT_KEEPALIVE=25
      - UI_TRAFFIC_STATS=true
      - UI_CHART_TYPE=1
    expose:
      - "51830/udp" # Exposes UDP port
      - "51821/tcp" # Exposes TCP port
    volumes:
      - ./etc_wireguard:/etc/wireguard
      - ./lib/modules:/lib/modules:ro
    restart: unless-stopped
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      # - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.default.forwarding=1
    networks:
      - pangolin

5. Exposing wg-easy via Pangolin

Step A: Add wg-easy to Pangolin’s network

If Pangolin is already running, attach wg-easy to the same external network (pangolin).

docker network connect pangolin wg-easy

Step B: Create a Local Site in Pangolin

  • Go to Sites → Add Site → Local
  • Name: wg-local

Step C: Add Resources

  • Web UI (HTTPS via Traefik)

    • Resource → Add Resource
    • Target: http://wg-easy:51821
    • Subdomain: vpn.example.com
    • Method: http
    • Enable TLS (Let’s Encrypt auto certs via Traefik).
  • WireGuard UDP Port

    • Resource → Add Resource → Raw UDP
    • Public Port: 51830
    • Target: wg-easy:51830
    • Protocol: UDP

This way:

  • Users hit vpn.example.com for the wg-easy dashboard (secured by Pangolin + Traefik).
  • WireGuard clients connect to vpn.example.com:51830/udp for the VPN tunnel.

I assume you know how to add ports in pangolin

Raw TCP & UDP - Pangolin Docs


6. Firewall Rules

On your VPS, allow:

sudo ufw allow 51830/udp

7. Verification

  • Visit https://vpn.example.com → wg-easy dashboard should load.
  • Download a WireGuard config from the UI.
  • Connect with wg-quick up wg0 or the WireGuard mobile app.
  • Traffic should route through your VPS.

8. Best Practices

  • Don’t expose 51821 directly — always proxy via Pangolin.
  • Use strong passwords for wg-easy.
  • Consider restricting wg-easy UI access with Pangolin’s Access Control (SSO, IP allowlist, etc.).
  • Monitor logs:
    docker logs -f wg-easy
    docker logs -f pangolin
    

That’s the cleanest way to run wg-easy + Pangolin together without port conflicts, while keeping everything proxied and secured.

3 Likes

That’s awesome!

What would be the modifications in case we use pangolin without gerbil (local only)?

Would that still work?

1 Like

yes it will still work, then you will have to put port reference in traefik.

services:
  traefik:
    image: traefik:v3.0 # Or your desired Traefik version
    container_name: traefik
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080" # Traefik Dashboard
      - "51820:51820/udp" # Custom UDP port
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik_dynamic:/etc/traefik/dynamic # Mount the dynamic config directory
    networks:
      - pangolin

networks:
  pangolin:
    external: true # Or define it here if not already existing

UDP port change not required because there is no gerbil. you can use default wg-eazy ports


if you change ports don’t forget to update here

Final Result

1 Like

Nice, working fine!

PS: tcp port 51831 not needed in traefik, only the udp one.

1 Like

Thank you for the write-up !

I’m unsure I’m getting you fully.

my pangolin compose file actually contains:

  gerbil:
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    command:
      - --reachableAt=http://gerbil:3003
      - --generateAndSaveKeyTo=/var/config/key
      - --remoteConfig=http://pangolin:3001/api/v1/gerbil/get-config
      - --reportBandwidthTo=http://pangolin:3001/api/v1/gerbil/receive-bandwidth
    container_name: gerbil
    depends_on:
      pangolin:
        condition: service_healthy
    image: fosrl/gerbil:1.2.1
    ports:
      - 51820:51820/udp
      - 443:443
      - 80:80
    restart: unless-stopped
    volumes:
      - ./config/:/var/config

So.

Do you mean I should change the pangolin compost file so that it exposes on another port (eg: - 51830:51820/udp) ?

Won’t that mess up the connection currently established by the newt on my homelab ?

Can’t I simply set wg-easy to listen on 51830 ? This will be set in the configurations generated for the clients and shouldn’t be problematic (although not standard for wireguard but who cares ?).

I mean:

volumes:
  etc_wireguard:

networks:
  pangolin:
    external: true   # use Pangolin's existing network

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - WG_HOST=vpn.example.com       # your domain
      - PASSWORD=supersecurepassword  # UI password
      - WG_PORT=51830                 # WireGuard port set to 51830 to avoid conflict with gerbil
      - WG_DEFAULT_DNS=1.1.1.1
    volumes:
      - etc_wireguard:/etc/wireguard
      - /lib/modules:/lib/modules:ro
    expose:
      - "51830/udp"   # WireGuard VPN (51530 to avoid conflict with gerbil)
      - "51821/tcp"   # Web UI (local only, proxied via Pangolin)
    restart: unless-stopped
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.default.forwarding=1
    networks:
      - pangolin

Thank you for clarifying.

1 Like

Thanks for the very detailed guide. I’ve tried your suggested setup, but wireguard client can’t access internet. I’ve opened port 51830/udp on Oracle VPS.

So far I’ve tried these setup combinations:

WG_PORT=51820, expose 51820/udp; pangolin resource: TCP public 51830 –> wg-easy 51820: no internet (using your suggested setup)

WG_PORT=51830, expose 51830/udp; pangolin resource: TCP public 51830 –> wg-easy 51830: no internet

WG_PORT=51830, ports 51830:51830/udp; pangolin resource: TCP public 51830 –> wg-easy 51830: working with internet access.

I’m not sure why your suggested setup doesn’t work for me.

1 Like

sorry I mistyped. All the above combinations. On pangolin, I mapped udp resource, not tcp.

1 Like

I had to comment out all the environment variables before the wg-easy container wanted to start. with these variables, I always got an error message about migrating from v14 to v15

2 Likes

@C8opmBMzz @europacafe @zorglups @Alexander_Federlin
Hi guys
I have attached screenshots. tested on all the 3 OS Ubuntu, Debian and Flatcar (On Pangolin with tunnels and local as well)

Let me know if still you guys face issues. I have kept the test bench running

1 Like

Please confirm that your guide is meant for case where you install wg-easy container on the same host as Pangolin?

and what you mean for this statement? Do I have to add port 51830 in gerbil and traefik docker compose?

I also understand that the udp resource should map to wg-easy:51830, not 51820
WireGuard UDP Port

  • Resource → Add Resource → Raw UDP
  • Public Port: 51830
  • Target: wg-easy:51820
  • Protocol: UDP
1 Like

This guide assumes you are installing WireGuard on a VPS for optimal performance. Although installation on a home network via newt is possible, it may result in slower speeds.
A common use case is running WireGuard alongside Pangolin, which is the scenario this guide has followed

Port Configuration to Avoid Conflicts

If you are running this setup on a VPS, you will likely have Gerbil installed as well. Both Gerbil and WireGuard use the same default network ports, which can cause a conflict. To resolve this, you must change the WireGuard port to 51830/UDP.
An eg. configuration is provided above in the guide, along with a link to a comprehensive guide on adding TCP/UDP resources to your server in pangolin.

Typo it should be wg-easy:51830 instead of wg-easy:51820 in the target

I have setup this for 2 use cases:

  • on my home nas, when I want my home ip in local mode - without gerbil
  • on my vps when I need DE ip

Thanks again, working amazing!

PS: would it be possible to have a similar setup with raw resources for the qbittorrrent port for seeding from home?

For example, now I have a custom TCP port forwarded on my home router (say 53222/tcp) which allows me to be discoverable and seed my iso images.

Can I remove that port somehow from the router and use pangolin, same as we did with the wireguard?

1 Like

I will see to it, the best possible way to deploy it. Thanks once again.

1 Like

Thank you for your clarification.

My platform: Oracle VPS, Pangolin, Gerbil, Traefik, wg-easy are on the same host

I have to bind port 51830 to host port 51830 to make it work.

When checking the network interface (ifconfig) inside wg-easy container, it uses eth1; so I have to modify the wg-easy admin page to eth1 too.

On Pangolin, I don’t have to create a raw udp resource proxy.

If I just “expose” port 51830 on wg-easy, the byte received is always 0, regardless of defining raw udp resource or not on Pangolin.

1 Like

See my above screenshots you have to login into the wg portal and change ports too.

yes. That’s what I did.

This is my working docker-compose:

services:
  wg-easy:
    #environment:
    #  Optional:
    #  - PORT=51821
    #  - HOST=0.0.0.0
    #  - INSECURE=false

    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    networks:
      pangolin:
      wg:
        ipv4_address: 10.42.42.42
        ipv6_address: fdcc:ad94:bacf:61a3::2a
    volumes:
      - /home/ubuntu/wg-easy:/etc/wireguard
      - /lib/modules:/lib/modules:ro
    ports:
      - "51830:51830/udp"
    expose:
      - "51821/tcp"
      #- "51830/udp"
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
      # - NET_RAW # ⚠️ Uncomment if using Podman
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.default.forwarding=1

networks:
  pangolin:
    external: true
  wg:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: 10.42.42.0/24
        - subnet: fdcc:ad94:bacf:61a3::/64
1 Like

It’s in two places one on config page and one on interface page. It should work.

Ping me on cord HHF Technology

Thanks. Everything is working with docker compose above. It is just different from your guide in two parts:

  1. for port 51830, just ‘expose’ it in docker compose + create raw UDP resources with public port 51830 and directs to local site wg-easy (container name) 51830 (container port), doesn’t work for me. I have to map the port to host and don’t need to create UDP resource on Pangolin.
  2. changing eth0 to eth1 in wg-easy admin page.
2 Likes