Servidor Dedicado no Godot 4: Como Exportar e Rodar Headless

Godot servidor dedicado na prática: export preset com a feature dedicated_server, flag --headless, rede com ENetMultiplayerPeer e deploy num VPS Linux.
Todo tutorial de multiplayer no Godot começa igual: um jogador clica em "criar sala", a máquina dele vira o host e os outros conectam no IP dele. Funciona para testar, funciona em LAN, e desmorona no momento em que você quer partidas pela internet que não dependam do roteador de ninguém. A resposta é montar um servidor dedicado no Godot 4: um processo do seu próprio jogo, sem janela e sem jogador, rodando num Linux com IP público, esperando conexões. Neste tutorial a gente faz o caminho completo: exportar o binário de servidor com o preset certo, rodar com --headless, escrever o main.gd que decide se aquele processo é servidor ou cliente, subir a rede com ENetMultiplayerPeer e colocar tudo no ar num VPS barato com systemd.
Este post assume que você já conhece o básico de rede na engine: @rpc, autoridade, peers. Se esses termos ainda são nebulosos, comece pelo post sobre RPC no multiplayer do Godot e volte aqui depois, porque tudo que vem a seguir constrói em cima disso.
Listen server ou servidor dedicado: qual a diferença
No modelo de listen server (o do "criar sala"), o host é servidor e jogador ao mesmo tempo. É o jeito mais barato de ter multiplayer: zero infraestrutura, zero custo mensal. Mas ele carrega três problemas estruturais.
O primeiro é disponibilidade. A partida vive na máquina do host. Ele fechou o jogo, caiu a internet dele, acabou a bateria do notebook? A sala inteira morre junto. O segundo é justiça: o host joga com latência zero enquanto todo mundo espera a viagem de ida e volta dos pacotes, o que em jogo de ação é vantagem real. O terceiro é o NAT: o IP do host é privado, atrás do roteador, então conexões vindas da internet aberta simplesmente não chegam sem port forwarding, coisa que nenhum jogador comum vai configurar.
O servidor dedicado resolve os três de uma vez. O processo do servidor roda numa máquina alugada com IP público, então qualquer cliente do mundo alcança. Ninguém joga nele, então ninguém tem vantagem. E ele fica no ar 24 horas por dia, independente de quem entra e sai. O preço é literal: você passa a pagar uma máquina por mês e a cuidar de deploy, logs e reinício. Para um indie, a boa notícia é que esse preço começa em poucos dólares, e a gente chega lá na parte de VPS.
O melhor detalhe da história: na Godot 4, servidor e cliente são o mesmo projeto. Você não mantém dois códigos. O mesmo repositório gera dois binários, um com interface e outro pelado, e um if na inicialização decide qual papel o processo assume.
Servidor dedicado no Godot 4: o export preset com a feature dedicated_server
O binário do servidor não precisa de texturas, modelos, música nem efeito sonoro. Ele só roda lógica e rede. A Godot 4 tem um modo de export feito exatamente para isso.
No editor, abra Project, depois Export, e clique em Add para criar um preset de Linux. Com o preset criado, vá na aba Resources e troque o Export Mode para "Export as dedicated server". Esse modo faz duas coisas importantes. A primeira: os recursos visuais e de áudio não são copiados para o pacote; no lugar deles entram placeholders, versões vazias que mantêm o mesmo caminho e o mesmo tipo, então o seu código carrega as cenas normalmente sem quebrar, só que sem os megabytes de PNG e OGG dentro. Um projeto que exporta com 500 MB para o jogador pode virar um binário de servidor com uma fração disso. A segunda: o preset ganha automaticamente a feature tag dedicated_server, e é essa tag que o código vai usar para saber em que modo está rodando.
A árvore de recursos na aba Resources deixa você ajustar o que vira placeholder e o que é mantido, o que é útil quando alguma cena do servidor realmente precisa de um recurso que o modo removeria. Na maioria dos projetos, o padrão já serve.
Exporte e você tem o binário Linux do servidor. O export normal de Windows, Linux ou Mac continua existindo do lado, intocado: esse é o que os jogadores baixam.
Um main.gd que decide entre servidor e cliente
Com a feature tag no binário, a cena principal do projeto vira um roteador de dois caminhos. OS.has_feature("dedicated_server") retorna true só no binário exportado com aquele preset:
extends Node
func _ready() -> void:
if OS.has_feature("dedicated_server"):
_subir_servidor()
else:
_subir_cliente()
func _subir_servidor() -> void:
print("Iniciando em modo servidor dedicado.")
get_tree().change_scene_to_file.call_deferred("res://cenas/servidor.tscn")
func _subir_cliente() -> void:
get_tree().change_scene_to_file.call_deferred("res://cenas/menu_principal.tscn")
O call_deferred existe porque trocar de cena no meio do _ready() da cena atual é pedir aviso do SceneTree; adiar para o fim do frame resolve. O cliente cai no menu normal, com botão de conectar. O servidor cai numa cena que não tem interface nenhuma, só um script de rede.
Esse if único é o que mantém o projeto são. Nada de branch separado no git, nada de "versão server" desatualizada: um código, dois binários, e o rumo decidido na primeira função que executa.
Subindo a rede com ENetMultiplayerPeer
A cena do servidor tem um trabalho: abrir a porta e reagir a quem entra e sai. O script é curto:
extends Node
const PORTA: int = 9000
const MAX_JOGADORES: int = 8
func _ready() -> void:
var peer := ENetMultiplayerPeer.new()
var erro: int = peer.create_server(PORTA, MAX_JOGADORES)
if erro != OK:
push_error("Nao consegui abrir a porta %d. Ja tem processo nela?" % PORTA)
get_tree().quit(1)
return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_ao_entrar_peer)
multiplayer.peer_disconnected.connect(_ao_sair_peer)
print("Servidor ouvindo na porta %d, ate %d jogadores." % [PORTA, MAX_JOGADORES])
func _ao_entrar_peer(id: int) -> void:
print("Peer %d conectou. Online agora: %d" % [id, multiplayer.get_peers().size()])
func _ao_sair_peer(id: int) -> void:
print("Peer %d desconectou." % id)
É o mesmo create_server() que você usaria num listen server, e essa é a graça: a API de rede não muda. Os sinais peer_connected e peer_disconnected disparam com o id de cada peer, e é a partir deles que você instancia o estado do jogador, registra nome, spawna personagem. Repare no get_tree().quit(1) quando a porta falha: num servidor sem tela, morrer com código de erro é melhor do que ficar vivo sem fazer nada, porque o systemd (daqui a pouco) detecta a falha e reinicia.
Do lado do cliente, nada de novo: create_client(ip_do_servidor, PORTA) apontando para o IP público do VPS em vez do IP do amigo na rede local. Todo o fluxo de registrar jogadores, sincronizar lista e travar sala é o mesmo que montei no tutorial de lobby multiplayer no Godot; a única diferença é que agora o peer 1 é uma máquina na nuvem em vez da máquina de um jogador.
Rodando com --headless
Exportou o binário de servidor? Teste na sua própria máquina antes de pagar VPS:
./meujogo_servidor.x86_64 --headless
A flag --headless inicia o processo sem janela, sem driver de vídeo e sem driver de áudio. Nenhuma renderização acontece, mas o SceneTree roda normal: física, timers, sinais e rede funcionam como sempre. É exatamente o que uma máquina de datacenter sem placa de vídeo precisa. Abra o jogo normal em outra janela, conecte em 127.0.0.1 e você tem o ciclo completo rodando localmente: um processo headless servindo, um cliente com tela jogando.
Dica que economiza sessão de debug: tudo que o servidor print() sai no stdout do terminal. Esse é o seu log. Escreva prints úteis desde já, com id de peer e contagem de jogadores, porque no VPS eles serão a única janela para dentro do processo.
Deploy num VPS Linux barato
O servidor de um jogo indie não pede muito: 1 vCPU e 1 GB de RAM dão conta de salas pequenas com lógica enxuta, e isso custa a faixa de 4 a 6 dólares por mês nos provedores comuns. Antes de escolher máquina e provedor, vale ler o post sobre quanto custa um servidor de jogo online, que abre essa conta em detalhe. Aqui vou direto à parte operacional.
Copie o export para o VPS e dê permissão de execução:
scp meujogo_servidor.x86_64 meujogo_servidor.pck deploy@SEU_IP:/home/deploy/meujogo/
ssh deploy@SEU_IP "chmod +x /home/deploy/meujogo/meujogo_servidor.x86_64"
ssh deploy@SEU_IP "sudo ufw allow 9000/udp"
A linha do firewall merece atenção: o ENet fala UDP. Liberar 9000/tcp e ficar olhando o cliente dar timeout é rito de passagem; libere a porta do create_server() em UDP, e confira também o firewall do painel do provedor, que em muitos deles é uma camada separada do ufw.
Rodar via screen funciona para testar ("abre uma sessão, roda o binário, desanexa"), mas se o processo cair de madrugada ele fica caído. Para deixar no ar de verdade, um serviço do systemd em /etc/systemd/system/meujogo.service resolve com dez linhas:
[Unit]
Description=Servidor dedicado do meu jogo
After=network.target
[Service]
User=deploy
WorkingDirectory=/home/deploy/meujogo
ExecStart=/home/deploy/meujogo/meujogo_servidor.x86_64 --headless
Restart=on-failure
[Install]
WantedBy=multi-user.target
Ative com sudo systemctl enable --now meujogo. A partir daí o servidor sobe sozinho no boot da máquina, reinicia sozinho se o processo morrer (lembra do quit(1) na falha de porta?) e os prints do jogo ficam acessíveis com journalctl -u meujogo -f. Deploy de versão nova é parar o serviço, copiar o binário novo por scp e subir de novo.
Autoridade do servidor em um parágrafo
Ter servidor dedicado só vale o custo se ele mandar de verdade. O princípio de server authority é: o cliente nunca afirma, o cliente pede. Ele não diz "meu personagem está na posição X" nem "acertei o tiro"; ele manda o input ou a intenção, o servidor simula, valida contra as regras do jogo e redistribui o estado oficial para todos. Todo dado que chega de um peer é tratado como potencialmente mentiroso, porque um cliente modificado pode mandar qualquer coisa. Na prática isso significa que as RPCs que os clientes podem chamar só recebem pedidos e validam, enquanto a simulação e o estado vivem em código que só executa onde multiplayer.is_server() é verdadeiro. Se você deixar o cliente decidir e o servidor apenas repassar, pagou VPS para hospedar um jogo tão trapaceável quanto o listen server que queria substituir.
Resumo do fluxo
O caminho inteiro cabe num parágrafo. No editor: preset de Linux com Export Mode em "Export as dedicated server", que remove visuais e áudio do pacote e adiciona a feature tag. No código: OS.has_feature("dedicated_server") no main.gd escolhe entre a cena do servidor e o menu do cliente, e a cena do servidor abre a porta com create_server(PORTA, MAX_JOGADORES) e escuta peer_connected e peer_disconnected. No terminal: --headless para rodar sem janela, primeiro local com cliente em 127.0.0.1, depois no VPS. Na máquina: porta liberada em UDP e um serviço do systemd com Restart=on-failure segurando o processo no ar.
Teste feio antes de anunciar: derrube clientes no meio da conexão, encha o servidor até o MAX_JOGADORES, mate o processo e veja o systemd ressuscitar. Servidor bom não é o que roda na demo, é o que continua rodando na terça de madrugada com jogador fazendo besteira. Com esse esqueleto no ar, o próximo passo natural é colocar a lógica de sala e de partida em cima dele, e essa parte você já sabe fazer.
Perguntas frequentes
Como fazer servidor dedicado no Godot?
Você adiciona um export preset de Linux no editor, muda o Export Mode para Export as dedicated server, exporta o binário e roda com a flag --headless num servidor Linux. No código, um ENetMultiplayerPeer com create_server() abre a porta e os sinais peer_connected e peer_disconnected avisam quando jogadores entram e saem.
O que a flag --headless faz no Godot?
Ela inicia o jogo sem janela, sem driver de vídeo e sem driver de áudio. O processo roda a lógica normalmente, incluindo física, timers e rede, mas não renderiza nada. É o modo certo para rodar num VPS sem placa de vídeo nem servidor gráfico, e também é como o Godot roda em pipelines de CI.
Como detectar em código se o jogo está rodando como servidor?
Use OS.has_feature("dedicated_server"). Essa feature tag é adicionada automaticamente ao binário quando o export preset usa o modo Export as dedicated server. Assim o mesmo projeto decide sozinho: se a tag existe, sobe o servidor; se não existe, abre o menu do cliente.
Servidor dedicado do Godot roda em VPS barato?
Roda. Um jogo indie com salas pequenas e lógica enxuta cabe num VPS de 1 vCPU e 1 GB de RAM, que custa poucos dólares por mês. Como o export de servidor dedicado remove texturas e áudio, o binário fica leve e o processo headless não gasta nada com renderização. O gargalo tende a ser a sua lógica de jogo, não a máquina.
Qual a diferença entre listen server e servidor dedicado?
No listen server, a máquina de um jogador é o servidor: se ele fechar o jogo ou cair, a partida morre, e ele joga com vantagem de latência zero. No servidor dedicado, um processo separado, sem jogador, hospeda a partida num IP público. Todo mundo conecta em pé de igualdade e a sala não depende de ninguém ficar online.
Preciso abrir porta no firewall para o servidor dedicado?
Sim, e o detalhe que pega muita gente: o ENet usa UDP, não TCP. Se o create_server() usa a porta 9000, libere 9000/udp no firewall do VPS, por exemplo com ufw allow 9000/udp, e também no painel do provedor se ele tiver firewall próprio.


