MySQL em outro servidor: abrir o banco sem abrir para a internet
Separe a aplicação do MySQL ajustando três travas: endereço de escuta, usuário amarrado ao IP da aplicação e firewall. Sem root e sem %.
Você moveu o MySQL para uma máquina própria, apontou a aplicação para o novo IP e veio o erro de conexão. Alguém sugere liberar o root para % e abrir a porta 3306 no firewall "só para testar". Funciona, e no dia seguinte a porta aparece em varredura automática. A resposta curta: o banco precisa de três travas ajustadas juntas. O MySQL escuta no IP privado, o usuário da aplicação existe só para o IP da aplicação, e o firewall deixa a porta passar só a partir desse IP. Nada de root, nada de %, nada de porta aberta para o mundo.
Separar aplicação e banco é um desenho saudável. Cada máquina cresce do seu jeito, e um pico de CPU do PHP não disputa recurso com a consulta pesada do relatório. O desenho só fica ruim quando a pressa de "fazer conectar" abre mais do que precisava.
Por que a conexão não entra, e por que abrir tudo é o erro
Numa instalação padrão, o MySQL escuta só em 127.0.0.1. Isso é proposital: ninguém de fora chega nele. Quando a aplicação muda de máquina, três coisas precisam mudar, e cada uma tem um sintoma diferente:
- Endereço de escuta. Se o MySQL só escuta em localhost, a conexão de fora é recusada na hora.
- Usuário e host. No MySQL, usuário é o par nome e origem.
'app'@'localhost'e'app'@'10.0.0.4'são contas diferentes, com senha e permissões próprias. Se não existe conta para a origem da aplicação, o servidor responde, mas recusa o login. - Firewall. O do sistema operacional e o do provedor de nuvem, quando há um. Se algum deles barra a porta, a conexão fica pendurada até estourar o tempo limite.
O atalho do root com % resolve as três de uma vez abrindo demais. Se a porta também está exposta, qualquer robô da internet passa a tentar senha nesse usuário, e um vazamento da senha da aplicação vira acesso administrativo ao banco inteiro.
Como liberar o MySQL só para a aplicação
Nos exemplos, o servidor do banco tem o IP privado 10.0.0.5 e a aplicação roda em 10.0.0.4, na mesma rede privada. Troque pelos seus.
- Escute no IP privado. No Ubuntu com MySQL, o arquivo costuma ser
/etc/mysql/mysql.conf.d/mysqld.cnf. No MariaDB, algo como/etc/mysql/mariadb.conf.d/50-server.cnf. Ajuste a linhabind-addresspara o IP privado, não para0.0.0.0, e reinicie o serviço. - Confirme onde ele escuta. Rode
sudo ss -tlnp | grep 3306. Você quer ver10.0.0.5:3306, não0.0.0.0:3306. - Crie o usuário da aplicação amarrado ao IP dela, com permissão só no schema dela.
- Libere a porta só para esse IP no firewall do sistema e no grupo de segurança do provedor.
- Teste a partir do servidor da aplicação, não do seu notebook.
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
bind-address = 10.0.0.5
mysqlx-bind-address = 127.0.0.1
O MySQL 8 também abre a porta do X Protocol, 33060, com configuração própria. Se a aplicação não usa, deixe essa escutando só localmente, como no exemplo.
CREATE USER 'loja_app'@'10.0.0.4' IDENTIFIED BY 'gere-uma-senha-longa-e-aleatoria';
GRANT SELECT, INSERT, UPDATE, DELETE ON loja.* TO 'loja_app'@'10.0.0.4';
Com CREATE USER e GRANT, não precisa de FLUSH PRIVILEGES. Esse comando só é necessário quando alguém mexe direto nas tabelas internas de permissão, o que você não deve fazer.
Repare que o usuário não tem CREATE, ALTER nem DROP. Se as migrations rodam no deploy, dê a elas um usuário separado, com essas permissões, usado só nesse momento. A aplicação que atende o público não precisa poder apagar tabela.
No firewall do servidor do banco, com ufw:
sudo ufw allow from 10.0.0.4 to any port 3306 proto tcp
sudo ufw status numbered
Confira se não sobrou uma regra antiga do tipo 3306 ALLOW Anywhere. Ela anula a regra restrita. No provedor de nuvem, a regra de entrada da porta 3306 deve ter como origem o IP da aplicação ou o grupo de segurança dela, nunca 0.0.0.0/0.
Por fim, do servidor da aplicação:
nc -zv 10.0.0.5 3306
mysql -h 10.0.0.5 -u loja_app -p loja -e "SELECT CURRENT_USER()"
O CURRENT_USER() mostra com qual conta o servidor te reconheceu. Se aparecer algo diferente de loja_app@10.0.0.4, outra conta mais genérica está casando primeiro.
Como ler o erro de conexão
Cada trava tem uma assinatura. Lendo a mensagem, você sabe qual falta, sem sair abrindo tudo:
- Tempo esgotado ao conectar. O pacote não chega. É firewall, do sistema ou do provedor, ou rota de rede.
- Conexão recusada. O pacote chega, mas ninguém escuta naquele IP e porta. É o
bind-addressou o serviço parado. - "Host ... is not allowed to connect". O MySQL respondeu, mas não existe conta para essa origem. Falta o usuário com o host certo.
- "Access denied for user". A conta existe, mas a senha não confere, ou o servidor casou sua conexão com outra conta.
Se do notebook entra e do servidor da aplicação não, a regra está liberando o lugar errado. Se dos dois entra sem VPN, a porta está no mundo.
Criptografia e acesso administrativo
Rede privada não é sinônimo de rede confiável. Em provedor de nuvem, o tráfego entre máquinas pode passar por infraestrutura compartilhada. O MySQL 8 suporta TLS, e você pode exigir conexão criptografada por usuário com REQUIRE SSL ou no servidor inteiro com require_secure_transport. No cliente, prefira o modo que verifica o certificado do servidor. Nunca resolva erro de certificado desligando a verificação.
Administração é outra conversa. Gente não conecta no banco direto da internet. Use VPN, um bastion ou um túnel SSH a partir do seu computador, com chave SSH em vez de senha:
ssh -L 3307:127.0.0.1:3306 voce@ip-publico-do-banco
# em outro terminal
mysql -h 127.0.0.1 -P 3307 -u admin -p
O túnel chega no MySQL como conexão local. O usuário administrativo pode existir só para localhost, e a porta 3306 continua fechada para fora. A senha da aplicação, por sua vez, mora em variável de ambiente ou num cofre de segredos, não no repositório. Se ela já foi para o Git, troque a senha antes de limpar o histórico.
Erros comuns
- Usar
bind-address = 0.0.0.0em máquina com IP público. O banco passa a escutar na interface pública, e só o firewall separa ele da internet. - Criar
'app'@'%'por preguiça. A senha da aplicação passa a valer de qualquer origem. Vazou, entrou. - Conectar a aplicação como root. Uma injeção de SQL deixa de ler uma tabela e passa a poder apagar o servidor inteiro.
- Esquecer o firewall do provedor. Você libera no
ufw, o grupo de segurança barra, e a conexão fica pendurada sem mensagem útil. - Deixar regra antiga de teste no firewall. A regra restrita existe, mas a genérica de "liberar 3306" também, e vence.
- Testar só do notebook. O teste passa pelo caminho errado e não prova nada sobre a aplicação.
Checklist
ss -tlnpmostra o MySQL escutando no IP privado, não em0.0.0.0.- O X Protocol, se não é usado, escuta só em localhost.
- O usuário da aplicação tem host igual ao IP da aplicação e permissão só no schema dela.
- Migrations usam outro usuário, que a aplicação em execução não conhece.
- Firewall do sistema e do provedor liberam a porta 3306 só para a aplicação.
- Uma tentativa de conexão a partir de um IP qualquer da internet expira.
- Administração passa por VPN, bastion ou túnel SSH.
- A senha do banco está em variável de ambiente ou cofre, fora do Git.
Rode agora, no servidor do banco, SELECT user, host FROM mysql.user; e olhe a coluna de host. Quantas contas aparecem com %, e alguma delas é a que a sua aplicação usa?
Referência: http://www.jack.eti.br/acessando-mysql-remotamente-seperando-aplicacao-php-do-banco-de-dados/