Projeto Servidor Web do Zero
Neste laboratório você transforma uma VM recém-criada num servidor web de verdade, configurado como um administrador faria. São nove etapas, e cada uma termina com uma conferência.
- Uma usuária própria com
sudo, acessando o servidor por SSH com chave, sem senha. - Um firewall que só deixa passar SSH e web.
- O nginx servindo o seu site, em
http://meusite.labehttps://meusite.lab. - Logs de acesso e de erro separados para o seu site.
| Item | Detalhe |
|---|---|
| Tempo estimado | 1h30 |
| Nível | Intermediário para avançado |
| Pré-requisitos | Como Usar os Projetos, sudo, Usuários e Grupos, SSH, ufw, nginx |
| Onde roda | VM lab do Multipass + seu notebook |
ana@notebook é o seu computador. ubuntu@lab e ana@lab são a VM. O IP usado aqui é 10.85.140.27; troque pelo IP que o multipass list mostrar para você.
As etapas
-
Preparar a VM
O que vamos fazer e por quê: criar a VM, tirar um snapshot (para poder recomeçar) e atualizar o sistema. Um servidor novo sempre começa atualizado, porque as imagens podem ter semanas de idade.
$ multipass launch 26.04 --name lab --disk 10G --memory 2G Launched: lab $ multipass stop lab $ multipass snapshot lab --name limpo Snapshot taken: lab.limpo $ multipass start lab $ multipass list Name State IPv4 Image lab Running 10.85.140.27 Ubuntu 26.04 LTS $ multipass shell lab
Agora, dentro da VM:
$ sudo apt update && sudo apt upgrade -y Hit:1 http://archive.ubuntu.com/ubuntu resolute InRelease Get:2 http://security.ubuntu.com/ubuntu resolute-security InRelease [126 kB] … Reading package lists... Done … 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. « os números variam
Confira se deu certo:
apt list --upgradablenão deve listar nada além deListing... Done. -
Criar a sua usuária com sudo
O que vamos fazer e por quê: o usuário
ubuntué genérico e existe em toda VM do Multipass. Num servidor real, cada administrador tem a própria conta: assim os logs mostram quem fez o quê, e é possível tirar o acesso de uma pessoa sem afetar as outras.- sudo
- Roda como administrador, porque criar usuários mexe em
/etc/passwd. - adduser
- Cria o usuário de forma interativa: pasta pessoal, senha e dados.
- ana
- O nome da nova conta.
$ sudo adduser ana info: Adding user `ana' ... info: Selecting UID/GID from range 1000 to 59999 ... info: Adding new group `ana' (1001) ... info: Adding new user `ana' (1001) with group `ana (1001)' ... info: Creating home directory `/home/ana' ... info: Copying files from `/etc/skel' ... New password: « digite a senha; nada aparece na tela Retype new password: passwd: password updated successfully Changing the user information for ana Enter the new value, or press ENTER for the default Full Name []: Ana Room Number []: Work Phone []: Home Phone []: Other []: Is the information correct? [Y/n] y $ sudo usermod -aG sudo ana $ groups ana ana : ana sudo users « "sudo" na lista = pode administrar
O
-aGsignifica "acrescente (append) ao grupo". Sem o-a, a usuária seria removida de todos os outros grupos.Confira se deu certo: entre como
anae use osudo:$ su - ana Password: « a senha da ana, criada agora há pouco
O prompt muda para
ana@lab. A partir daqui você é a ana:$ sudo whoami [sudo: authenticate] Password: root « o sudo funcionou $ exit logout
Se deu errado: "ana is not in the sudoers file" ou "I'm sorry ana"O que aconteceu: o
usermodnão rodou, ou a sessão da ana foi aberta antes de ela entrar no grupo. Grupos novos só valem a partir do próximo login.Como resolver: confira com
groups ana. Sesudonão aparecer, rode de novosudo usermod -aG sudo anacomoubuntu. Depois saia (exit) e entre de novo comoana. -
Acesso por SSH com chave
O que vamos fazer e por quê: senhas podem ser adivinhadas; uma chave SSH, não. Você gera um par de chaves no notebook: a privada fica com você, e a pública vai para o servidor. As VMs do Multipass já vêm com o login por senha desligado, então a chave é o único jeito de entrar por SSH.
No notebook, veja se você já tem uma chave. Se não tiver, crie uma:
$ ls ~/.ssh/id_ed25519.pub ls: não é possível acessar '/home/ana/.ssh/id_ed25519.pub': Arquivo ou diretório inexistente $ ssh-keygen -t ed25519 -C "ana@notebook" Generating public/private ed25519 key pair. Enter file in which to save the key (/home/ana/.ssh/id_ed25519): « Enter aceita o padrão Enter passphrase for "/home/ana/.ssh/id_ed25519" (empty for no passphrase): « uma frase-senha protege a chave Enter same passphrase again: Your identification has been saved in /home/ana/.ssh/id_ed25519 Your public key has been saved in /home/ana/.ssh/id_ed25519.pub …
Num servidor comum, você usaria
ssh-copy-id. Como a VM não aceita senha por SSH, vamos copiar a chave com o próprio Multipass:$ multipass transfer ~/.ssh/id_ed25519.pub lab:/tmp/chave.pub $ multipass shell lab
Dentro da VM, coloque a chave no lugar certo, com o dono e as permissões que o SSH exige:
$ sudo install -d -m 700 -o ana -g ana /home/ana/.ssh $ sudo install -m 600 -o ana -g ana /tmp/chave.pub /home/ana/.ssh/authorized_keys $ sudo ls -la /home/ana/.ssh total 12 drwx------ 2 ana ana 4096 Oct 15 10:40 . drwxr-x--- 3 ana ana 4096 Oct 15 10:35 .. -rw------- 1 ana ana 94 Oct 15 10:40 authorized_keys « 600: só a ana lê e escreve $ exit
- install
- Copia arquivos ou cria pastas já com dono e permissão, num passo só.
- -d
- Cria uma pasta (directory) em vez de copiar um arquivo.
- -m 700
- Permissão (mode): só o dono entra.
- -o ana -g ana
- Dono (owner) e grupo.
- /home/ana/.ssh
- A pasta onde o SSH procura as chaves da ana.
Agora, do notebook, conecte direto como
ana:$ ssh ana@10.85.140.27 The authenticity of host '10.85.140.27 (10.85.140.27)' can't be established. ED25519 key fingerprint is SHA256:Xq3n…kP8. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes « só na primeira vez Warning: Permanently added '10.85.140.27' (ED25519) to the list of known hosts. Welcome to Ubuntu 26.04 LTS (GNU/Linux 7.0.0-14-generic x86_64) …
Confira se deu certo: rode um comando remoto. Se aparecer
anasem pedir senha do servidor, a chave funciona:$ ssh ana@10.85.140.27 whoami ana
Endurecer o SSH
Vamos garantir, num arquivo nosso, que ninguém entra como root nem por senha. Arquivos em
/etc/ssh/sshd_config.d/são lidos em ordem alfabética, e o primeiro valor encontrado vale. Por isso o nome começa com10-.$ sudo nano /etc/ssh/sshd_config.d/10-seguranca.conf
# Ninguém entra direto como root PermitRootLogin no # Só chave, nunca senha PasswordAuthentication no KbdInteractiveAuthentication no
$ sudo sshd -t « testa a configuração; silêncio = sem erro $ sudo systemctl reload ssh
Não feche a sessão atualAntes de sair, abra outro terminal no notebook e teste
ssh ana@10.85.140.27. Se a nova conexão falhar, você ainda tem a antiga aberta para consertar.Se deu errado: "Permission denied (publickey)"$ ssh ana@10.85.140.27 ana@10.85.140.27: Permission denied (publickey).
O que aconteceu: o servidor não aceitou a chave. As causas mais comuns são a chave no arquivo errado, ou a pasta
.sshe oauthorized_keyscom dono ou permissão errados. O SSH recusa chaves em pastas que outras pessoas podem alterar.Como confirmar:
ssh -v ana@10.85.140.27mostra quais chaves o notebook ofereceu. Na VM, comoubuntu:sudo ls -la /home/ana/.sshesudo journalctl -u ssh -n 20.Como resolver: refaça os dois comandos
installdesta etapa. -
Ligar o firewall
O que vamos fazer e por quê: por padrão, o firewall vem desligado. Vamos bloquear tudo o que chega e liberar só o SSH. Libere o SSH antes de ligar; senão você se tranca para fora.
$ sudo ufw status Status: inactive $ sudo ufw app list Available applications: OpenSSH $ sudo ufw allow OpenSSH Rules updated Rules updated (v6) $ sudo ufw enable Command may disrupt existing ssh connections. Proceed with operation (y|n)? y Firewall is active and enabled on system startup
Confira se deu certo:
$ sudo ufw status Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6)
Do notebook, uma nova conexão
ssh ana@10.85.140.27deve continuar funcionando.Se deu errado: você se trancou para foraO que aconteceu: o firewall foi ligado sem a regra do SSH.
Como resolver: no laboratório, o Multipass tem uma porta dos fundos que não passa pela rede:
multipass exec lab -- sudo ufw allow OpenSSH. Num servidor real, você precisaria do console do provedor de nuvem. É por isso que a ordem importa. -
Instalar o nginx
O que vamos fazer e por quê: o nginx é o programa que responde aos navegadores. Depois de instalado, liberamos as portas 80 (HTTP) e 443 (HTTPS) no firewall.
$ sudo apt install -y nginx … Setting up nginx (1.28.0-1ubuntu1) ... « a versão varia $ systemctl is-active nginx active $ sudo ufw allow 'Nginx Full' Rules updated Rules updated (v6)
O nome
Nginx Fullestá entre aspas por causa do espaço. Ele libera as portas 80 e 443 de uma vez.Confira se deu certo: do notebook, peça só o cabeçalho da página com
curl -I:$ curl -I http://10.85.140.27 HTTP/1.1 200 OK « 200 = a página existe e foi entregue Server: nginx/1.28.0 (Ubuntu) Content-Type: text/html …
Se deu errado: "Connection timed out" ou "Failed to connect"O que aconteceu: ou o firewall não liberou a porta 80, ou o nginx não está rodando.
Como confirmar: na VM,
sudo ufw statusdeve mostrarNginx Full, esudo ss -tlnp | grep :80deve mostrar o nginx escutando.Como resolver:
sudo ufw allow 'Nginx Full'esudo systemctl start nginx. -
Publicar o seu site
O que vamos fazer e por quê: cada site ganha uma pasta em
/var/wwwe um arquivo de configuração próprio. Assim, um servidor pode hospedar vários sites sem misturar nada.Crie a pasta e a página. A pasta fica com a
anacomo dona, para você editar semsudo:$ sudo mkdir -p /var/www/meusite $ sudo chown -R ana:ana /var/www/meusite $ nano /var/www/meusite/index.html
<!doctype html> <html lang="pt-BR"> <head><meta charset="utf-8"><title>Meu site</title></head> <body> <h1>Funcionou!</h1> <p>Servidor montado no laboratório do Guia Ubuntu.</p> </body> </html>
Agora, a configuração do site no nginx (um server block):
$ sudo nano /etc/nginx/sites-available/meusite
server { listen 80; listen [::]:80; server_name meusite.lab; # o nome do site root /var/www/meusite; # a pasta com os arquivos index index.html; access_log /var/log/nginx/meusite.access.log; error_log /var/log/nginx/meusite.error.log; location / { try_files $uri $uri/ =404; # arquivo, pasta ou "não encontrado" } }No Ubuntu, um site só fica ativo quando existe um link para ele em
sites-enabled. Vamos ativar o nosso e desativar o site padrão:$ sudo ln -s /etc/nginx/sites-available/meusite /etc/nginx/sites-enabled/ $ sudo rm /etc/nginx/sites-enabled/default « apaga só o link; o original continua em sites-available $ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful $ sudo systemctl reload nginx
Semprenginx -tantes do reloadO
-ttesta a configuração sem aplicar. Se houver erro, o site atual continua no ar e você conserta com calma.O nome
meusite.labnão existe na internet. Para o notebook encontrá-lo, adicione uma linha no/etc/hostsdo notebook:$ echo "10.85.140.27 meusite.lab" | sudo tee -a /etc/hosts [sudo: authenticate] Senha: 10.85.140.27 meusite.lab
Confira se deu certo:
$ curl http://meusite.lab <!doctype html> <html lang="pt-BR"> … <h1>Funcionou!</h1> « a SUA página, não a do nginx
Abra
http://meusite.labno navegador do notebook também.Se deu errado: aparece "Welcome to nginx!" em vez do seu siteO que aconteceu: o site padrão ainda está ativo e respondeu primeiro.
Como confirmar:
ls -l /etc/nginx/sites-enabled/deve mostrar sómeusite.Como resolver:
sudo rm /etc/nginx/sites-enabled/defaultesudo systemctl reload nginx.Se deu errado: "403 Forbidden"O que aconteceu: o nginx (que roda como
www-data) não consegue ler a pasta, ou não háindex.html.Como confirmar:
sudo tail /var/log/nginx/meusite.error.logmostra o motivo, por exemplopermission deniedoudirectory index … is forbidden.Como resolver: confira o nome do arquivo e use
ls -ld /var/www/meusite: a pasta precisa de permissão de leitura e entrada para "outros" (drwxr-xr-x). -
Ler os logs
O que vamos fazer e por quê: todo acesso ao site vira uma linha no log. É assim que você descobre quem acessou, o que pediu e o que deu errado.
$ sudo tail -f /var/log/nginx/meusite.access.log
Deixe esse comando rodando e, no notebook, acesse uma página que existe e outra que não existe:
$ curl -s -o /dev/null -w "%{http_code}\n" http://meusite.lab/ 200 $ curl -s -o /dev/null -w "%{http_code}\n" http://meusite.lab/nada 404Na VM, aparecem duas linhas novas:
$ sudo tail -f /var/log/nginx/meusite.access.log 10.85.140.1 - - [15/Oct/2026:13:42:13 +0000] "GET / HTTP/1.1" 200 214 "-" "curl/8.14.1" 10.85.140.1 - - [15/Oct/2026:13:42:20 +0000] "GET /nada HTTP/1.1" 404 162 "-" "curl/8.14.1" ^C « Ctrl+C para parar de acompanhar
Trecho Significado 10.85.140.1IP de quem acessou (o notebook, visto pela VM) [15/Oct/2026:13:42:13 +0000]Data e hora, no fuso UTC "GET /nada HTTP/1.1"O que foi pedido 404A resposta: 200 = ok, 404 = não existe, 403 = proibido, 500 = erro no servidor 162Tamanho da resposta em bytes "curl/8.14.1"O programa que fez o pedido (navegador, curl, robô…) Confira se deu certo: conte quantos erros 404 já aconteceram:
$ sudo awk '$9 == 404' /var/log/nginx/meusite.access.log | wc -l 1
-
HTTPS com certificado autoassinado
O que vamos fazer e por quê: o HTTPS cifra a conversa entre o navegador e o servidor. Um certificado de verdade exige um domínio público; no laboratório, criamos um autoassinado: ele cifra igual, mas o navegador avisa que não conhece quem o assinou.
$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/ssl/private/meusite.key \ -out /etc/ssl/certs/meusite.crt \ -subj "/CN=meusite.lab" …- openssl req -x509
- Gera um certificado autoassinado.
- -nodes
- Sem senha na chave; senão o nginx pediria a senha a cada reinício.
- -days 365
- Validade de um ano.
- -newkey rsa:2048
- Cria uma chave nova junto.
- -keyout / -out
- Onde salvar a chave privada e o certificado.
- -subj "/CN=meusite.lab"
- Para qual nome o certificado vale.
Acrescente um segundo bloco
serverno fim de/etc/nginx/sites-available/meusite:server { listen 443 ssl; listen [::]:443 ssl; server_name meusite.lab; ssl_certificate /etc/ssl/certs/meusite.crt; ssl_certificate_key /etc/ssl/private/meusite.key; root /var/www/meusite; index index.html; access_log /var/log/nginx/meusite.access.log; error_log /var/log/nginx/meusite.error.log; location / { try_files $uri $uri/ =404; } }$ sudo nginx -t && sudo systemctl reload nginx nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Confira se deu certo: do notebook. Sem
-k, o curl recusa o certificado; com-k(de insecure), ele aceita mesmo assim:$ curl https://meusite.lab curl: (60) SSL certificate problem: self-signed certificate « esperado: ninguém conhecido assinou … $ curl -sk https://meusite.lab | grep h1 <h1>Funcionou!</h1>
E com um domínio de verdade?
Num servidor na internet, com um domínio apontando para o IP dele e as portas 80 e 443 abertas, o certificado é gratuito e reconhecido por todos os navegadores. O
certbot, do Let's Encrypt, faz tudo, inclusive a renovação automática. Veja HTTPS (Let's Encrypt).$ sudo apt install -y certbot python3-certbot-nginx $ sudo certbot --nginx -d meudominio.com.br
-
Revisão final
O que vamos fazer e por quê: conferir tudo de uma vez, como faria antes de entregar o servidor a alguém.
$ ssh ana@10.85.140.27 'sudo ufw status | head -1; systemctl is-active nginx ssh' Status: active active active $ curl -s -o /dev/null -w "%{http_code}\n" http://meusite.lab 200 $ curl -sk -o /dev/null -w "%{http_code}\n" https://meusite.lab 200 $ ssh root@10.85.140.27 root@10.85.140.27: Permission denied (publickey). « root bloqueado, como queríamos
O que você aprendeu
- Criar um administrador com
adduser+usermod -aG sudo. - Instalar uma chave SSH com dono e permissões corretas, e desligar senha e root no SSH.
- Ligar o ufw depois de liberar o SSH.
- Instalar o nginx, criar um server block, ativá-lo com um link e testar com
nginx -t. - Ler o log de acesso e interpretar os códigos 200, 403 e 404.
- Gerar um certificado autoassinado e servir o site em HTTPS.
Desafios extras
- Redirecione todo acesso HTTP para HTTPS com
return 301 https://$host$request_uri;no bloco da porta 80. - Crie um segundo site,
blog.lab, na mesma VM, com pasta e logs próprios. - Instale o
fail2bane veja-o bloquear tentativas de login (veja Segurança do Servidor). - Faça um script que mostre, por dia, quantos acessos o site recebeu.
Desfazer e limpar
No notebook, remova a linha do /etc/hosts e apague a VM (ou volte ao snapshot, para reaproveitá-la):
$ sudo sed -i '/meusite.lab/d' /etc/hosts $ ssh-keygen -R 10.85.140.27 « esquece a "impressão digital" do servidor $ multipass delete --purge lab
Exercícios
1. Testar antes de aplicar
Complete o comando que testa a configuração do nginx sem aplicá-la:
2. Liberar o SSH no firewall
Complete o comando que deve rodar antes de sudo ufw enable:
3. Por que usamos usermod -aG sudo ana e não usermod -G sudo ana?
4. O log mostra "GET /admin HTTP/1.1" 404. O que isso quer dizer?