Configurar uma VPN WireGuard no seu próprio servidor

Publicado a 16 min de leitura

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-tools recomenda 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 linhas iptables -t nat -A POSTROUTING que toda a gente copia não fazem ali nada enquanto não instalar o pacote.
  • Entrega do DNS: para a linha DNS =, o wg-quick chama sem mais o programa resolvconf. No Ubuntu, o pacote systemd-resolved traz consigo uma camada de compatibilidade; numa instalação mínima de Debian muitas vezes nem sequer existe um resolvconf. Para o instalar, o pacote certo chama-se openresolv no 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_forward está 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:

  1. 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] PrivateKey ficar 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.key tem de dar exatamente o conteúdo de server.pub.
  2. 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 wg responde a isso com Key is not the correct length or format.
  3. Permissões do ficheiro. Se se esquecer do umask 077, o wg-quick avisa no arranque com Warning: `/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 com tcpdump -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 tcpdump mostrar pacotes. Nesse caso o WireGuard está à escuta noutra porta ou noutro endereço. Controla-se com ss -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:

SistemaPacote adequado
Debian 11, Debian 12, Debian 13apt-get install -y openresolv
Ubuntu 22.04apt-get install -y openresolv
Ubuntu 24.04apt-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?
As quatro trazem a mesma versão upstream das ferramentas, 1.0.20210914, cada uma com a revisão própria da distribuição. O módulo WireGuard em si vem do kernel desde a versão 5.6 e não tem de ser compilado à parte. Os comandos wg e wg-quick comportam-se de forma idêntica nos quatro sistemas, as diferenças estão no filtro de pacotes (nftables ou iptables), no resolvconf e no ufw. Para instalar o resolvconf, o pacote chama-se openresolv no Debian e no Ubuntu 22.04; no Ubuntu 24.04 o openresolv já não existe e instala-se o resolvconf.
Porque é que as linhas de iptables de muitos tutoriais não funcionam no Debian?
O pacote wireguard-tools recomenda nftables ou iptables como alternativa. O apt instala a primeira alternativa disponível, ou seja, o nftables. Numa instalação Debian enxuta o iptables nem sequer está presente, a linha PostUp falha e isso aborta todo o arranque do wg-quick. Ou usa a variante nft, ou instala o iptables de forma explícita.
O handshake funciona, mas não chego à Internet. A que se deve?
Verifique por esta ordem: o net.ipv4.ip_forward tem de estar a 1, a regra de NAT tem de indicar a interface de saída correta (o ip route show default mostra-a) e, com o ufw ativo, é preciso ainda ufw route allow in on wg0 out on eth0. Com o ufw, o interruptor do kernel por si só não chega, porque o ufw aplica uma política de encaminhamento própria.
Como reconheço um problema de MTU?
O típico é isto: o túnel está ativo, o ping e o SSH funcionam, mas as páginas web carregam-se pela metade e os downloads grandes interrompem-se. Defina MTU = 1280 a título de teste na secção [Interface] do cliente. Se o problema desaparecer, era a MTU. O valor ideal determina-se com ping -M do contra o endereço do servidor, baixando a carga útil até passar, somando depois 28 e subtraindo 80.
Preciso de um servidor DNS próprio para o DNS funcionar dentro do túnel?
Não. Pode simplesmente indicar um resolver público na configuração do cliente, que é depois alcançado através do túnel. Um resolver próprio no servidor (por exemplo dnsmasq em wg0) compensa se quiser resolver nomes internos ou colocar os pedidos em cache. Importante é apenas isto: se indicar DNS = 10.8.0.1, tem mesmo de existir ali um servidor de nomes à escuta.
Porque é que um dispositivo deixa de passar depois de acertar o relógio?
O WireGuard protege-se contra reinjeção com uma marca temporal na primeira mensagem de handshake. O servidor guarda por cada peer o valor mais alto que viu e descarta os mais antigos. Se um dispositivo com o relógio adiantado para o futuro chegou a ligar-se uma vez, passa a ser rejeitado depois da correção da hora. Esse estado fica em memória, por isso ajuda um wg-quick down wg0 seguido de um wg-quick up wg0 no servidor.

WireGuard VPN Debian Ubuntu nftables Redes Tutorial