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.

Ao terminar você terá
  • 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.lab e https://meusite.lab.
  • Logs de acesso e de erro separados para o seu site.
ItemDetalhe
Tempo estimado1h30
NívelIntermediário para avançado
Pré-requisitosComo Usar os Projetos, sudo, Usuários e Grupos, SSH, ufw, nginx
Onde rodaVM lab do Multipass + seu notebook
Preste atenção no prompt

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

  1. 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 --upgradable não deve listar nada além de Listing... Done.

  2. 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 -aG significa "acrescente (append) ao grupo". Sem o -a, a usuária seria removida de todos os outros grupos.

    Confira se deu certo: entre como ana e use o sudo:

    $ 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 usermod nã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. Se sudo não aparecer, rode de novo sudo usermod -aG sudo ana como ubuntu. Depois saia (exit) e entre de novo como ana.

  3. 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 ana sem 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 com 10-.

    $ 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 atual

    Antes 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 .ssh e o authorized_keys com dono ou permissão errados. O SSH recusa chaves em pastas que outras pessoas podem alterar.

    Como confirmar: ssh -v ana@10.85.140.27 mostra quais chaves o notebook ofereceu. Na VM, como ubuntu: sudo ls -la /home/ana/.ssh e sudo journalctl -u ssh -n 20.

    Como resolver: refaça os dois comandos install desta etapa.

  4. 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.27 deve continuar funcionando.

    Se deu errado: você se trancou para fora

    O 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.

  5. 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 Full está 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 status deve mostrar Nginx Full, e sudo ss -tlnp | grep :80 deve mostrar o nginx escutando.

    Como resolver: sudo ufw allow 'Nginx Full' e sudo systemctl start nginx.

  6. Publicar o seu site

    O que vamos fazer e por quê: cada site ganha uma pasta em /var/www e 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 ana como dona, para você editar sem sudo:

    $ 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
    Sempre nginx -t antes do reload

    O -t testa a configuração sem aplicar. Se houver erro, o site atual continua no ar e você conserta com calma.

    O nome meusite.lab não existe na internet. Para o notebook encontrá-lo, adicione uma linha no /etc/hosts do 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.lab no navegador do notebook também.

    Se deu errado: aparece "Welcome to nginx!" em vez do seu site

    O 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/default e sudo 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.log mostra o motivo, por exemplo permission denied ou directory 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).

  7. 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
    404

    Na 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
    TrechoSignificado
    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
  8. 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 server no 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
  9. 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

  1. Redirecione todo acesso HTTP para HTTPS com return 301 https://$host$request_uri; no bloco da porta 80.
  2. Crie um segundo site, blog.lab, na mesma VM, com pasta e logs próprios.
  3. Instale o fail2ban e veja-o bloquear tentativas de login (veja Segurança do Servidor).
  4. 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:

sudo nginx

2. Liberar o SSH no firewall

Complete o comando que deve rodar antes de sudo ufw enable:

sudo ufw allow

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?