wg-easy: WireGuard com painel web no Docker
Configure o wg-easy no Docker Compose: portas, NET_ADMIN, sysctls necessários e QR codes para celulares, incluindo a mudança de configuração da versão 15.
O que você vai criar
O wg-easy é o WireGuard com uma interface web, executado como um único contêiner Docker. Ele gere a interface WireGuard por você e adiciona uma interface no navegador para criar clientes. Cada cliente criado recebe um ficheiro de configuração e um código QR. Assim, um telefone entra na VPN ao apontar a câmara para o ecrã.
O túnel em si é WireGuard normal. O módulo do kernel transporta os pacotes, pelo que o débito é igual ao de uma configuração escrita manualmente. O que você ganha é a gestão do ciclo de vida dos clientes: adicionar, desativar e eliminar peers sem editar um ficheiro de configuração através de SSH. O que você perde é o controlo direto desse ficheiro, que é o tema de da configuração manual do WireGuard num VPS.
Você precisa de um VPS KVM com um endereço IPv4 público, Docker Engine com o plugin Compose e acesso root. A virtualização de contêineres que partilha o kernel do host, como OpenVZ ou LXC, normalmente não consegue carregar o módulo WireGuard, e o contêiner falha ao ativar a interface.
A versão 15 retirou as definições do ambiente
A maioria dos guias disponíveis foi escrita para o wg-easy 14, no qual WG_HOST era definido como o endereço do servidor e PASSWORD_HASH como um hash bcrypt da palavra-passe de administrador, ambos como variáveis de ambiente. A versão 15 é uma reescrita. As notas oficiais de migração indicam claramente que a v15 não utiliza as mesmas variáveis de ambiente da v14 e que a maioria delas foi transferida para o painel de administração na interface web.
Por isso, WG_HOST e PASSWORD_HASH já não fazem nada. Se copiar um ficheiro compose antigo, o contentor inicia, ignora essas linhas e pede-lhe para criar uma conta de administrador no browser. Isto não é um erro. É o novo fluxo de configuração.
Em julho de 2026, a tag principal a fixar é 15. Fixe a versão principal em vez de utilizar latest, porque uma atualização de versão principal altera o formato da configuração em disco e não permite um rollback limpo.
O ficheiro compose
Crie um diretório para a stack e escreva nele o ficheiro compose oficial. Este é o ficheiro upstream, sem alterações.
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.ymlO conteúdo é semelhante a este:
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
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:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard é um volume nomeado que contém a chave do servidor e todos os clientes que criar. Faça uma cópia de segurança desse volume. Caso contrário, uma recriação elimina todos os seus peers. Se preferir ver esses ficheiros no sistema de ficheiros do host, substitua-o por um bind mount. Antes disso, leia a diferença entre bind mounts e volumes nomeados, porque as permissões funcionam de forma diferente.
Por que ele precisa de NET_ADMIN, SYS_MODULE e dos sysctls
Um container não pode acessar a pilha de rede por padrão, e cada uma destas linhas remove um bloqueio específico.
NET_ADMIN permite que o container crie a interface wg0, atribua-lhe um endereço e escreva rotas. Sem isso, o container inicia e depois termina ao ativar a interface, porque ip link add wg0 type wireguard retorna Operation not permitted.
SYS_MODULE, juntamente com a montagem somente leitura de /lib/modules, permite que o container carregue o módulo WireGuard do kernel se o host ainda não o tiver carregado. O módulo fica no kernel do host, não dentro da imagem, por isso o diretório do host precisa estar visível. Num kernel moderno, o módulo normalmente já está incorporado, e pode confirmar isso com sudo modprobe wireguard && echo ok no host.
net.ipv4.ip_forward=1 faz o kernel encaminhar pacotes que não são destinados ao próprio servidor. Sem isso, um cliente conecta-se, o handshake é concluído e, depois, todos os pacotes para a Internet são descartados. Por isso, ping 1.1.1.1 expira enquanto a VPN parece estar conectada.
net.ipv4.conf.all.src_valid_mark=1 é o que mais surpreende. O WireGuard marca os próprios pacotes de saída para que eles não sejam encaminhados de volta para o túnel. A filtragem rigorosa do caminho inverso identifica um pacote cujo endereço de origem não corresponde à rota esperada e descarta-o. Este sysctl instrui o kernel a aceitar pacotes marcados, impedindo que um túnel completo interrompa o próprio funcionamento.
Inicie-o e crie a conta de administrador
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fUse docker compose up e docker compose down, não start e stop. O projeto upstream avisa que start num contentor criado com definições diferentes deixa a rede num estado inconsistente. Se quiser que a stack volte a arrancar depois de um reboot, restart: unless-stopped já trata disso, e o comportamento no boot dos serviços compose explica o que essa política garante e o que não garante.
A interface web escuta na porta TCP 51821. Na primeira visita, apresenta uma página de configuração onde cria a conta de administrador e confirma o endereço do host que os clientes vão usar para chegar ao servidor. Esse endereço do host é incluído na linha Endpoint de todas as configurações dos clientes, por isso tem de ser o IP público ou o nome DNS da VPS. Se estiver errado, o código QR que fornece a um telemóvel aponta para um destino inacessível e o handshake nunca é concluído.
Há mais um detalhe sobre essa porta: wg-easy 15 recusa HTTP simples, a menos que defina INSECURE=true. Aceder através de HTTPS com um certificado não confiável ou terminar o TLS num reverse proxy à frente do serviço são opções válidas. Aceder através de http:// com as definições predefinidas não é.
Não publique a porta da interface web na Internet
O ficheiro compose publica a porta 51821 em todas as interfaces. Essa é uma página de início de sessão para uma máquina que pode encaminhar o seu tráfego e não deve estar aberta para toda a Internet. A publicação de uma porta no Docker escreve regras na cadeia DOCKER, que é avaliada antes do ufw. Por isso, uma regra ufw deny não fecha essa porta. Vale a pena compreender esta armadilha por si só. Por que motivo as portas publicadas pelo Docker ignoram o ufw explica o assunto em detalhe.
A correção simples consiste em associar a interface web ao loopback e aceder-lhe através de um túnel SSH:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=trueDepois, no seu portátil:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressAbra http://127.0.0.1:51821 no navegador do seu portátil. O tráfego é encriptado pelo SSH, a porta não responde a mais ninguém e INSECURE=true é seguro neste caso, porque o salto HTTP simples nunca sai da interface de loopback.
Abra a porta UDP 51820 e verifique as duas firewalls
O WireGuard precisa que a porta UDP 51820 esteja acessível a partir da internet. O Docker publica essa porta, mas muitos provedores colocam uma firewall de rede separada à frente do VPS, que não é conhecida pelo Docker. Abra a porta nos dois locais. Se gerir a firewall do host com ufw, as regras básicas do ufw para um VPS são uma opção mais simples do que escrever regras nftables manualmente.
Verifique se o contentor está efetivamente a escutar:
sudo ss -ulnp | grep 51820Deverá ver um socket UDP em estado de escuta. Se não aparecer nada nessa linha, o contentor nunca ativou a interface, e sudo docker compose logs wg-easy indicará o motivo.
Crie um cliente e faça a leitura no telemóvel
Na interface, crie um cliente e atribua-lhe um nome que reconheça mais tarde, como o dispositivo a que pertence. O wg-easy atribui o próximo endereço livre do túnel e gera o par de chaves por si. Cada linha de cliente disponibiliza um código QR e um ficheiro .conf para transferência.
Instale a aplicação oficial WireGuard no telemóvel, escolha adicionar um túnel a partir de um código QR e aponte a câmara para o código no ecrã. O túnel aparece com o nome que introduziu. Ative-o. A linha do cliente na interface começa a apresentar contadores de transferência e a hora do handshake mais recente. Quando um telemóvel está ligado ao túnel, pode aceder a serviços que nunca publicou na internet. É assim que um telemóvel continua a carregar fotografias para um servidor de fotografias autoalojado a partir de qualquer lugar, sem que esse servidor tenha uma única porta aberta para o mundo. O mesmo princípio aplica-se a conteúdos multimédia. Uma biblioteca Jellyfin reconstruída como uma videoclube dos anos 90 é agradável de consultar num quarto de hotel, mantendo-se tão privada como na sua LAN. Os alertas funcionam no sentido inverso no mesmo túnel. Um servidor ntfy autoalojado pode enviar uma mensagem para esse telemóvel no momento em que uma tarefa de backup falha, sem nunca responder a um pedido da internet pública.
Um cliente que não apresenta nenhum handshake depois de ser ativado não está a chegar ao servidor. Isso aponta para a UDP 51820, seja na firewall do fornecedor, seja no endereço do endpoint incluído na configuração. Um cliente que apresenta um handshake, mas não tem acesso funcional à internet, aponta para o encaminhamento ou para o DNS.
Num desktop, transfira o ficheiro .conf e importe-o para o cliente WireGuard, em vez de o introduzir novamente. A chave privada desse ficheiro é gerada uma vez e apresentada uma vez. Trate o ficheiro como trataria uma chave privada SSH.
Quando deixar de usar a interface
O wg-easy é a ferramenta certa enquanto os seus peers são pessoas e telefones. A interface é mais rápida do que editar ficheiros de configuração, e revogar um telefone perdido requer apenas um clique.
Os limites aparecem quando precisa de algo que a interface não representa. O encaminhamento entre sites, em que o AllowedIPs de um peer abrange toda uma sub-rede remota em vez de um único endereço, é normalmente o primeiro obstáculo. Túneis divididos com regras de encaminhamento por peer ou uma configuração gerada pela sua ferramenta de provisionamento são os passos seguintes. Nessa altura, a configuração manual não é mais difícil. É apenas diferente. O guia simples do WireGuard mostra o mesmo túnel criado a partir de wg0.conf. Se preferir deixar de executar o plano de controlo, WireGuard comparado com Tailscale apresenta a opção gerida. A justiça dessa troca depende do que o servidor de coordenação consegue realmente alcançar. Vale a pena ler o modelo de confiança do Tailscale antes de lhe entregar a sua rede. O custo costuma ser a questão seguinte. O que o plano gratuito do Tailscale cobre na prática é suficiente para que uma família ou uma equipa pequena não pague nada. Depois desse limite, a faturação conta utilizadores em vez de dispositivos. É uma estrutura de custos diferente da de um VPS que já paga. Por isso, consulte quanto custa o Tailscale depois de ultrapassar o plano gratuito antes de migrar uma equipa. O túnel completo que acabou de criar tem um equivalente direto nesse serviço. Anunciar o VPS como um exit node do Tailscale fornece a mesma rota de saída através do servidor. A aprovação é feita na consola de administração, em vez de ser escrita na configuração de cada cliente. A limitação das sub-redes também tem um equivalente. Anunciar uma rede privada completa a partir do VPS disponibiliza essa rede a todos os dispositivos da tailnet, sem a edição de AllowedIPs por peer que o levou a deixar de usar a interface. Se quiser esse painel e o encaminhamento automático em malha, mas não quiser usar o servidor de coordenação de outra pessoa, executar o seu próprio servidor NetBird num VPS mantém o plano de controlo em hardware que possui. Em contrapartida, terá de configurar o DNS e o TLS, algo que o wg-easy nunca lhe exigiu.
Se a sintaxe do Compose apresentada acima era a parte desconhecida, e não o WireGuard, noções básicas do Docker Compose num VPS explica o formato do ficheiro e os comandos utilizados no dia a dia.
FAQ
Por que o wg-easy ignora WG_HOST e PASSWORD_HASH?
Essas variáveis pertencem ao wg-easy 14. A versão 15 foi reescrita, e o projeto upstream transferiu quase toda a configuração para o painel de administração da interface web. O contentor não lê nenhuma das variáveis. Por isso, inicia normalmente e pede-lhe para criar uma conta de administração na primeira visita. Defina o endereço do host visível para os clientes nessa página de configuração.
Preciso de SYS_MODULE se o meu kernel já tiver WireGuard?
Não. SYS_MODULE e a montagem /lib/modules existem para que o contentor possa carregar o módulo quando o host não o tem. Num host onde sudo modprobe wireguard já é executado com sucesso, essa capacidade não é utilizada. Removê-la é uma medida razoável de reforço da segurança, e NET_ADMIN continua a ser necessário em qualquer caso.
O cliente liga-se, mas não há internet. Qual é o problema?
Um handshake sem tráfego quase sempre indica um problema de encaminhamento. Confirme se net.ipv4.ip_forward=1 e net.ipv4.conf.all.src_valid_mark=1 continuam no ficheiro compose, porque uma cópia editada manualmente pode perder essas definições. Se o encaminhamento estiver ativo, verifique o servidor DNS recebido pelo cliente. Um túnel que envia todo o tráfego pela VPN, mas aponta para um servidor DNS que já não consegue alcançar, aparece exatamente como uma ligação inativa no navegador.
Como faço uma cópia de segurança dos meus clientes?
Tudo está no volume nomeado etc_wireguard, num ficheiro wg0.json. A interface também tem um botão de cópia de segurança que exporta os mesmos dados. Copie esse ficheiro para fora do servidor antes de qualquer atualização. O restauro é feito através de um carregamento durante a etapa de configuração de um contentor novo.
Posso executar o wg-easy atrás de um reverse proxy?
Sim. Coloque o proxy à frente do TCP 51821, termine o TLS nesse proxy e defina INSECURE=true no contentor para que aceite o salto HTTP simples vindo do proxy. Publique diretamente o UDP 51820, porque o tráfego da VPN usa UDP e não passa por um proxy HTTP.