Configurar uma VPN WireGuard no seu próprio servidor
Do servidor vazio ao túnel WireGuard a funcionar: pares de chaves, NAT com nftables, wg-quick como serviço, código QR para o telemóvel e os três problemas em que isto realmente encalha.
O que é igual nas quatro distribuições e o que não é
O WireGuard está firmemente integrado no kernel do Linux desde a versão 5.6. Por isso, em Debian 13, Debian 12, Ubuntu 24.04 e Ubuntu 22.04 já não precisa de compilar um módulo DKMS, nem de acrescentar um repositório de backports, nem de importar qualquer chave externa. As quatro trazem além disso o mesmo estado das ferramentas de userland (versão upstream 1.0.20210914), ou seja: os comandos wg e wg-quick comportam-se de forma idêntica em todas.
As diferenças estão apenas em tudo o que rodeia o WireGuard, e é precisamente aí que a maioria dos tutoriais falha:
- Filtro de pacotes: o
wireguard-toolsrecomenda nftables ou iptables. Como o apt instala a primeira alternativa disponível, numa instalação Debian enxuta acaba por ficar o nftables, não o iptables. As linhasiptables -t nat -A POSTROUTINGque toda a gente copia não fazem ali nada enquanto não instalar o pacote. - Entrega do DNS: para a linha
DNS =, owg-quickchama sem mais o programaresolvconf. No Ubuntu, o pacotesystemd-resolvedtraz consigo uma camada de compatibilidade; numa instalação mínima de Debian muitas vezes nem sequer existe umresolvconf. Para o instalar, o pacote certo chama-seopenresolvno Debian e no Ubuntu 22.04, e no Ubuntu 24.04 esse pacote já não existe. Isto afeta apenas os clientes Linux, não os telemóveis. - Frontend de firewall: as imagens de Ubuntu trazem com frequência um ufw ativo, o Debian em regra não. O ufw bloqueia o encaminhamento por predefinição, mesmo quando
net.ipv4.ip_forwardestá a 1.
Verifique primeiro o que tem à sua frente:
apt-get update
apt-get install -y wireguard wireguard-tools nftables qrencode
wg --version
apt-cache policy wireguard-tools
Se o wg --version devolver um número de versão, o userland está presente. Se o kernel colabora, só o descobre no primeiro wg-quick up. Se aí ficar a mensagem RTNETLINK answers: Operation not supported ou Unable to access interface: Protocol not supported, está a correr um kernel sem suporte para WireGuard, tipicamente um kernel muito antigo ou um kernel de container bastante reduzido.
Gerar os pares de chaves sem criar problemas
O WireGuard não conhece nomes de utilizador nem certificados. Existe exatamente um par de chaves por participante e, opcionalmente, uma preshared key comum como camada simétrica adicional. Gere ambos com a umask definida, caso contrário as chaves privadas ficam no disco legíveis para toda a gente:
umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
wg genkey | tee /etc/wireguard/telemovel.key | wg pubkey > /etc/wireguard/telemovel.pub
wg genpsk > /etc/wireguard/telemovel.psk
ls -l /etc/wireguard
Não precisa de criar o diretório /etc/wireguard, o pacote wireguard-tools já o traz com o modo 0700. Mais importante é uma particularidade da umask: o valor só vale dentro da sessão de shell em que o define. Quem gerar as chaves em duas etapas, por exemplo porque a ligação caiu entretanto e voltou a iniciar sessão, trabalha depois outra vez com a máscara predefinida, e tanto o server.key como o telemovel.psk acabam no disco com 0644. Por isso, defina as permissões mais uma vez de forma explícita no final:
chmod 600 /etc/wireguard/*.key /etc/wireguard/*.psk
Há três erros que se repetem sempre:
- Trocar a chave pública com a privada. Ambas são strings Base64 de 44 caracteres e têm exatamente o mesmo aspeto. Se em
[Interface] PrivateKeyficar por descuido uma chave pública, o túnel arranca à mesma, mas nunca chega a haver um handshake. Isso pode ser derivado a qualquer momento:wg pubkey < /etc/wireguard/server.keytem de dar exatamente o conteúdo deserver.pub. - Copiar também a quebra de linha. As chaves copiadas com o rato a partir de um terminal arrastam com gosto espaços atrás. O
wgresponde a isso comKey is not the correct length or format. - Permissões do ficheiro. Se se esquecer do
umask 077, owg-quickavisa no arranque comWarning: `/etc/wireguard/wg0.conf' is world accessible. Isto não é cosmética, neste ficheiro está a chave privada em texto simples.
A configuração do servidor
Determine primeiro o nome da sua interface de Internet. Em máquinas virtuais chama-se eth0, ens3 ou enp1s0, consoante a imagem:
ip route show default
Crie depois /etc/wireguard/wg0.conf. A rede do túnel deve ser uma que não lhe apareça pelo caminho no Wi-Fi de um hotel, portanto de preferência nada de 192.168.0.0/24 ou 192.168.1.0/24:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = CONTEUDO_DE_SERVER_KEY
PostUp = nft add table ip wgnat
PostUp = nft add chain ip wgnat postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
PostUp = nft add rule ip wgnat postrouting ip saddr 10.8.0.0/24 oifname "eth0" masquerade
PostDown = nft delete table ip wgnat
[Peer]
# telemovel
PublicKey = CONTEUDO_DE_TELEMOVEL_PUB
PresharedKey = CONTEUDO_DE_TELEMOVEL_PSK
AllowedIPs = 10.8.0.2/32
Substitua eth0 pela sua interface real. Quem preferir ficar pelo iptables instala o pacote iptables e usa antes:
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
Dois pormenores que raramente são mencionados. Primeiro: cada linha PostUp é executada através de uma shell e, se uma delas terminar com erro, o wg-quick reverte todo o arranque. Um erro de escrita na regra nft não leva, portanto, a um túnel meio funcional, mas sim a nenhum túnel. Segundo: AllowedIPs tem do lado do servidor um significado completamente diferente do que tem do lado do cliente. Aqui é uma tabela de correspondência que diz qual o endereço de origem que pertence a que peer. Se indicar o mesmo endereço em dois peers, ganha o que for carregado por último, e o outro fica mudo. Cada cliente recebe exatamente uma /32.
Defina as permissões e verifique a sintaxe antes de arrancar:
chmod 600 /etc/wireguard/wg0.conf
wg-quick strip wg0
O wg-quick strip mostra a configuração sem as linhas próprias do wg-quick. Se o comando correr até ao fim, o ficheiro está formalmente em ordem. Se devolver wg-quick: Line unrecognized, o mais habitual é ter escrito uma opção na secção errada, por exemplo DNS por baixo de [Peer].
Ativar o encaminhamento de IP de forma permanente
Sem encaminhamento, cada pacote termina no servidor. Ative-o de imediato e de forma permanente:
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf
sysctl --system
sysctl net.ipv4.ip_forward
O último comando tem de devolver net.ipv4.ip_forward = 1. Se quiser enviar IPv6 pelo túnel, o mesmo ficheiro precisa ainda de net.ipv6.conf.all.forwarding=1.
Se o ufw estiver a correr, isso ainda não chega. O ufw aplica uma política de encaminhamento própria, que atua de forma independente do interruptor do kernel. Abra a porta e permita a passagem de forma explícita:
ufw allow 51820/udp
ufw route allow in on wg0 out on eth0
O quadro de erro típico quando se esquece o encaminhamento é especialmente traiçoeiro: o handshake funciona, o ping para 10.8.0.1 funciona, mas nada do que está por trás fica acessível. Quem olha apenas para o handshake passa horas a procurar no sítio errado.
Configurar o wg-quick como serviço
O pacote traz uma unidade de template, o nome a seguir ao @ é o nome do ficheiro de configuração sem extensão:
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
systemctl status wg-quick@wg0
journalctl -u wg-quick@wg0 -n 50 --no-pager
Uma armadilha que muitos só notam ao fim de semanas: SaveConfig = true. Esta opção escreve o estado em execução de volta para o ficheiro quando o serviço é parado. Nesse processo perdem-se todos os comentários, todas as linhas PostUp pela sua ordem original e qualquer estrutura inserida à mão. Para um servidor cuja configuração mantém à mão, deixe a opção de fora.
Depois do arranque, não avalie o estado pelo código de saída, mas sim pela interface:
wg show
ip -brief address show wg0
ss -ulpn
O ss -ulpn tem de mostrar um processo à escuta na porta UDP 51820. O wg show lista os peers, nesta altura ainda sem handshake.
Configuração do cliente e código QR para o telemóvel
O ficheiro do cliente convém gerá-lo no servidor, porque é lá que estão de qualquer forma todas as chaves. Atenção: do lado do cliente, AllowedIPs significa outra coisa, nomeadamente que destinos devem passar pelo túnel. 0.0.0.0/0, ::/0 quer dizer: tudo.
mkdir -p /etc/wireguard/clients
Conteúdo de /etc/wireguard/clients/telemovel.conf:
[Interface]
PrivateKey = CONTEUDO_DE_TELEMOVEL_KEY
Address = 10.8.0.2/32
DNS = 9.9.9.9, 149.112.112.112
[Peer]
PublicKey = CONTEUDO_DE_SERVER_PUB
PresharedKey = CONTEUDO_DE_TELEMOVEL_PSK
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = seu.servidor.endereco:51820
PersistentKeepalive = 25
O PersistentKeepalive = 25 não é nenhum luxo nos telemóveis. O NAT das redes móveis esquece as associações UDP muitas vezes ao fim de 30 a 60 segundos e, sem keepalive, o servidor deixa de conseguir contactar por iniciativa própria uma ligação existente.
O código QR gera-o diretamente no terminal:
qrencode -t ansiutf8 < /etc/wireguard/clients/telemovel.conf
Na aplicação do WireGuard toque no mais, escolha a importação por código QR e aponte a câmara ao terminal. Dois conselhos práticos: reduza o tamanho da letra no terminal antes de gerar o código, caso contrário não cabe na imagem. E não apague o ficheiro logo a seguir, vai precisar dele quando trocar de dispositivo. Quem o apagar mesmo assim tem de gerar um novo par de chaves, porque a chave privada não pode ser recalculada a partir da pública.
Como reconhecer que está mesmo a funcionar
O facto de o systemctl start voltar sem qualquer saída significa apenas que a interface existe. O teste verdadeiro tem três níveis e deve percorrê-los por esta ordem:
wg show wg0 latest-handshakes
wg show wg0 transfer
Nível um, o handshake. O latest-handshakes devolve uma marca temporal Unix por cada peer. Se estiver lá 0, nunca se chegou a estabelecer uma ligação. Na vista detalhada do wg show lê-se em vez disso latest handshake: 42 seconds ago.
Nível dois, o fluxo de dados. Em transfer aparecem os bytes recebidos e os enviados. Só bytes enviados e nenhum recebido significa: os seus pacotes saem, mas não volta nada. Isso é quase sempre uma firewall ou um endpoint errado, nunca um problema de chaves.
Nível três, o caminho real. No cliente, verifique se o tráfego é mesmo encaminhado para o túnel:
ip route get 1.1.1.1
Se aparecer dev wg0, o encaminhamento está correto. Só depois disso faz sentido abrir uma página que mostre o seu endereço IP público. Se ela mostrar o endereço do seu servidor, está feito.
Diagnóstico: não há handshake
O quadro de erro mais frequente de todos. Ative primeiro o registo do módulo do kernel, que costuma dar a resposta em dez segundos:
echo 'module wireguard +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w
Agora inicie uma tentativa de ligação no cliente e vá lendo. As três mensagens que interessam:
wireguard: wg0: Handshake for peer 1 (...) did not complete after 5 seconds, retrying (try 2)e mais nada. Ao servidor não chega um único pacote. Confirme comtcpdump -n -i eth0 udp port 51820. Se aí não vir nada, a causa está na firewall à frente do servidor, na porta, ou no facto de o cliente estar numa rede que filtra o UDP de saída. Um teste a partir da rede móvel em vez do Wi-Fi da empresa separa estes casos com clareza.wireguard: wg0: Invalid handshake initiation from .... Chegam pacotes, mas não encaixam. Quase sempre a chave pública do servidor no ficheiro do cliente não está correta, ou a preshared key só está indicada de um lado. Uma preshared key tem de ser idêntica dos dois lados ou faltar dos dois lados.- Nenhuma mensagem, apesar de o
tcpdumpmostrar pacotes. Nesse caso o WireGuard está à escuta noutra porta ou noutro endereço. Controla-se comss -ulpn.
Um caso especial pouco documentado é a hora. O WireGuard coloca na primeira mensagem de handshake uma marca temporal e o servidor guarda por cada peer o valor mais alto alguma vez visto. As marcas mais antigas são descartadas, e essa é a proteção contra reinjeção. Se um dispositivo colocou o relógio muito à frente e chegou a ligar-se uma vez, deixa de passar depois de corrigir a hora. O servidor mantém esse estado em memória, por isso o que ajuda é: acertar o relógio e depois, no servidor, wg-quick down wg0 e wg-quick up wg0. Depois de voltar a levantar, o bloqueio desaparece.
Desligue o registo a seguir, porque é bastante falador:
echo 'module wireguard -p' > /sys/kernel/debug/dynamic_debug/control
Diagnóstico: o DNS não funciona
Sintoma: o túnel está ativo, o ping 1.1.1.1 funciona, mas nenhum nome é resolvido. Há exatamente três causas.
Primeira: o resolver não está acessível. Se indicar DNS = 10.8.0.1, tem mesmo de existir no servidor um servidor de nomes à escuta nesse endereço. Um Debian ou um Ubuntu acabados de instalar não têm nenhum. Ou indica um resolver público, alcançado através do túnel, ou instala um por sua conta. Para o segundo caminho chega o dnsmasq com um ficheiro pequeno em /etc/dnsmasq.d/wireguard.conf:
interface=wg0
bind-dynamic
no-resolv
server=9.9.9.9
server=1.1.1.1
cache-size=1000
O bind-dynamic é importante porque, no arranque do dnsmasq, o wg0 pode ainda não existir. O no-resolv é obrigatório no Ubuntu: sem essa linha, o dnsmasq lê o /etc/resolv.conf, encontra ali o endereço stub 127.0.0.53 do systemd-resolved e monta um ciclo. E o ponto que mais morde no Ubuntu: o dnsmasq ocupa a porta 53. Num sistema com systemd-resolved ativo essa porta já está tomada, os dois serviços colidem e o dnsmasq não arranca. As linhas interface=wg0 e bind-dynamic não são, por isso, cosmética, elas evitam que o dnsmasq se ligue a todos os endereços. Verifique a seguir com ss -ulpn que o dnsmasq e o systemd-resolved não estão a disputar a porta 53.
Segunda: o resolver fica fora dos AllowedIPs. Quem, em vez de 0.0.0.0/0, envia apenas algumas redes pelo túnel e indica como servidor DNS um endereço que não consta dessa lista, manda os pedidos por fora do túnel, para a rede local.
Terceira, apenas em clientes Linux: falta o resolvconf. A linha DNS = é entregue pelo wg-quick a um programa chamado resolvconf. Se ele faltar, o arranque é interrompido com /usr/bin/wg-quick: line 32: resolvconf: command not found. Verifique primeiro se o programa sequer existe:
command -v resolvconf
Na instalação as distribuições separam-se, e é precisamente aí que falham os tutoriais copiados quando são aplicados ao Ubuntu 24.04. Ali o pacote openresolv não existe nem em main nem em universe, e a chamada termina com E: Package 'openresolv' has no installation candidate:
| Sistema | Pacote adequado |
|---|---|
| Debian 11, Debian 12, Debian 13 | apt-get install -y openresolv |
| Ubuntu 22.04 | apt-get install -y openresolv |
| Ubuntu 24.04 | apt-get install -y resolvconf |
No Ubuntu 24.04, o resolvconf é um pacote virtual que se resolve sem ambiguidade para systemd-resolved e que cria de caminho o /usr/sbin/resolvconf, ou seja, exatamente o binário que o wg-quick chama para a linha de DNS. Ali pode instalar igualmente bem o systemd-resolved diretamente. Quem quiser uma linha que funcione em todos os sistemas mencionados, usa esta:
apt-get install -y openresolv || apt-get install -y resolvconf
No Ubuntu 24.04 existe uma variante disto que aparece depois de uma atualização a partir do 22.04: Failed to resolve interface "tun.wg0": No such device. A causa é um pacote resolvconf antigo que ficou do 22.04, juntamente com o /etc/resolvconf/interface-order, ou seja, não o pacote virtual com o mesmo nome do 24.04. Com base nesse ficheiro, o wg-quick antepõe tun. ao nome da interface, e a camada de compatibilidade do systemd-resolved não consegue fazer nada com isso. A solução é remover o pacote antigo, de modo que fique apenas a camada do systemd-resolved.
Diagnóstico: problemas de MTU
O quadro de erro mais desagradável, porque tudo parece funcionar. O handshake está de pé, o ping corre, o SSH corre, mas as páginas web carregam-se pela metade e ficam penduradas, os downloads grandes interrompem-se, e logo o HTTPS é o afetado. A razão: os pacotes pequenos passam, os grandes não.
O WireGuard acrescenta 60 bytes a cada pacote quando o túnel corre sobre IPv4 (20 bytes de IP, 8 bytes de UDP, 32 bytes de WireGuard) e 80 bytes sobre IPv6. Por isso o wg-quick subtrai de forma geral 80 bytes à MTU de caminho detetada e, num percurso normal de 1500, fica em 1420. Isto é deliberadamente conservador e está certo na maioria dos casos.
Não está certo quando o caminho é mais estreito do que 1500, por exemplo em DSL com PPPoE (1492), atrás de outro túnel ou em algumas redes móveis. Meça a MTU de caminho real a partir do cliente até ao endereço público do servidor, com o bit Don't Fragment ativo e sem túnel:
ping -M do -s 1472 -c 3 ENDERECO_DESTINO
1472 mais 28 bytes de cabeçalho dão 1500. Se voltar ping: local error: message too long, mtu=... ou Frag needed and DF set, baixe o valor por etapas até passar: 1464, 1444, 1414, 1372. Ao valor encontrado some 28 e subtraia 80. Com 1464 seriam então 1492 de MTU de caminho e 1412 de MTU do túnel.
Isto é indicado na secção [Interface], do lado que tem o problema:
MTU = 1412
A contraprova rápida, antes de se pôr a fazer contas: experimente MTU = 1280. É a MTU mais pequena que o IPv6 garante e funciona praticamente em todo o lado. Se as suas páginas carregarem bem assim, era a MTU, e pode aproximar-se com calma do valor ideal. Se o problema se mantiver, está noutro sítio.
No próprio servidor uma MTU errada é menos frequente, mas possível: se estiver lá um valor mais alto do que o percurso permite, só nota o efeito em determinados destinos. Um olhar a ip -brief address show wg0 e a ip link show wg0 mostra o valor atualmente definido.
Alterar peers em produção sem deitar todos fora
O reflexo de escrever systemctl restart wg-quick@wg0 depois de cada alteração deita abaixo todas as ligações existentes e reconstrói as regras de NAT. Num servidor com vários utilizadores isso é desnecessariamente bruto. O WireGuard consegue alinhar a configuração com o serviço a correr:
wg syncconf wg0 <(wg-quick strip wg0)
O comando compara o ficheiro com o estado em execução e altera apenas as diferenças. Os peers existentes mantêm a sua sessão. Tenha em conta que a substituição de processos com <(...) pressupõe uma bash ou zsh, numa sh pura falha. Um peer isolado também pode ser adicionado diretamente:
wg set wg0 peer CHAVE_PUBLICA allowed-ips 10.8.0.3/32
Esta alteração vive apenas em memória. Escreva-a também no ficheiro de configuração, caso contrário o peer desaparece no próximo reinício. Essa é, aliás, a causa mais frequente da frase "ontem ainda funcionava".
Se quiser operar um endpoint WireGuard de forma permanente e com um endereço estável, um servidor próprio é a base óbvia. Na KernelHost, os servidores root KVM e os servidores dedicados correm no datacenter maincubes em Frankfurt am Main (TÜV TIER3+) dentro da nossa própria rede, PrePaid e sem prazo mínimo. A completar, são interessantes os nossos artigos sobre a proteção do SSH e sobre a configuração do ufw.
Perguntas frequentes
Que versão do WireGuard vem no Debian 13, Debian 12, Ubuntu 24.04 e Ubuntu 22.04?
Porque é que as linhas de iptables de muitos tutoriais não funcionam no Debian?
O handshake funciona, mas não chego à Internet. A que se deve?
Como reconheço um problema de MTU?
Preciso de um servidor DNS próprio para o DNS funcionar dentro do túnel?
Porque é que um dispositivo deixa de passar depois de acertar o relógio?
2026 KernelHost GmbH. Todos os direitos reservados. Este guia está protegido por direitos de autor. A sua republicação noutros sites, na íntegra, em parte ou de forma editada, não é permitida sem o nosso consentimento por escrito. Citações com indicação da fonte e ligação são expressamente bem-vindas.

