Repor a palavra-passe root do MySQL e do MariaDB
Esqueceu-se da palavra-passe root? Muitas vezes nem precisa de nenhuma. E se precisar mesmo: veja como abrir a base de dados durante exatamente um minuto, sem a entregar a meio mundo.
A palavra-passe root da base de dados é uma daquelas palavras-passe que se define uma vez durante a instalação e de que depois nunca mais se precisa, porque todas as aplicações trabalham com contas próprias. Até ao dia em que é mesmo preciso lá voltar. E então aparece isto:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
A resposta habitual na Internet é: parar o serviço, arrancar com --skip-grant-tables, definir a palavra-passe, pronto. Está correto, mas é apenas metade da verdade. Neste guia fica também a saber porque é que o comando copiado por toda a parte não produz qualquer efeito no Ubuntu com MySQL 8, porque é que em muitos casos nem sequer precisa de uma palavra-passe, e o que fazer quando, a seguir, o serviço deixa de arrancar de vez.
Verifique primeiro: é provável que nem precise de palavra-passe
No Debian e no Ubuntu, o root da base de dados há anos que já não está protegido por uma palavra-passe, mas sim pela identidade do utilizador do sistema. O MariaDB chama a esse método unix_socket, o MySQL chama-lhe auth_socket. O significado é o mesmo: quem se liga através do socket local e já é root ao nível do sistema operativo entra sem palavra-passe. A razão por trás disto é simples: um utilizador de sistema root chega de qualquer forma a todos os ficheiros de dados e à memória do processo, pelo que uma palavra-passe adicional não representa obstáculo nenhum.
A primeira tentativa deve por isso ser sempre esta:
sudo mariadb
sudo mysql
Repare bem: sudo mysql -u root -p não funciona, porque o -p comuta para autenticação por palavra-passe. É exatamente aqui que a maioria falha. Sem -p e com sudo, aterra normalmente logo no prompt. A partir daí define a nova palavra-passe em dois segundos, sem sequer mexer na base de dados.
A segunda via de recurso é a conta de manutenção da distribuição. No Ubuntu com o pacote mysql-server continua a existir o ficheiro /etc/mysql/debian.cnf, que contém as credenciais de uma conta com privilégios totais, legível apenas pelo root:
sudo ls -l /etc/mysql/debian.cnf
sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "SELECT current_user();"
Se voltar uma linha, está lá dentro e pode continuar de imediato. Não espere, no entanto, ver root@localhost: a consulta responde com debian-sys-maint@localhost. É assim mesmo, porque esta conta de manutenção tem ALL PRIVILEGES e chega perfeitamente para a reposição, mas com ela não é root. Este caminho é além disso específico do Debian e do Ubuntu. No AlmaLinux, no Rocky Linux e no Oracle Linux não existe nem o diretório /etc/mysql/ nem a conta, e aí vale antes o início de sessão por socket como root descrito na secção anterior. No MariaDB, a partir da versão 10.4 esta conta já não é criada de raiz (o ficheiro remete normalmente apenas para o root através do socket), mas em instalações antigas continua muitas vezes presente. Espreitar não custa nada e, se resultar, poupa-lhe todo o resto deste guia.
Afinal, que base de dados está a correr aqui?
Isto não é uma formalidade, é o que decide cada um dos comandos seguintes. No Debian já não existe há anos qualquer pacote mysql-server no arquivo oficial, e aí corre praticamente sempre MariaDB, mesmo quando o comando mysql existe. É que esse comando é apenas uma ligação simbólica para o cliente do MariaDB. Neste momento, encontra nas distribuições o seguinte:
| Sistema | mysql-server | mariadb-server |
| Debian 13 | não existe | 11.8 |
| Debian 12 | não existe | 10.11 |
| Ubuntu 24.04 | 8.0 | 10.11 |
| Ubuntu 22.04 | 8.0 | 10.6 |
Pergunte ao próprio servidor, não ao cliente:
systemctl list-units --type=service --all | grep -Ei 'mysql|mariadb'
mysqladmin --version
mariadbd --version
O --all faz parte do comando, porque de outro modo o list-units só mostra unidades carregadas e ativas. Precisamente no caso em que precisa deste comando, ou seja, com um serviço que não está a correr, a saída ficaria vazia. O mysqladmin --version existe dos dois lados e indica o motor de forma explícita: os sistemas com MariaDB respondem com ... Distrib 11.8.6-MariaDB .... No caso do mariadbd --version, um command not found num sistema exclusivamente MySQL é o resultado esperado e não um erro; ao contrário, o mesmo vale para comandos específicos do mysqld num sistema exclusivamente MariaDB.
Se a unidade se chamar mariadb.service, está a trabalhar com MariaDB. Se se chamar mysql.service, com MySQL. Nos sistemas com MariaDB existe adicionalmente um alias mysql.service que aponta para mariadb.service, e por isso a saída do systemctl list-units diz mais do que a mera existência de um nome.
Os próprios nomes das unidades diferem entre as famílias de distribuições, e todas as chamadas systemctl deste guia estão escritas para Debian e Ubuntu. Na família Red Hat não existe sequer uma unidade chamada mysql.service, e aí o systemctl status mysql responde com Unit mysql.service could not be found. Nesse caso, use sempre os nomes da coluna da direita, inclusive no caminho do diretório de drop-in:
| O quê | Debian e Ubuntu | AlmaLinux, Rocky, RHEL |
|---|---|---|
| Unidade MariaDB | mariadb.service | mariadb.service |
| Unidade MySQL | mysql.service | mysqld.service |
| Diretório de drop-in do MySQL | /etc/systemd/system/mysql.service.d/ | /etc/systemd/system/mysqld.service.d/ |
| Binário do servidor MySQL | /usr/sbin/mysqld | /usr/libexec/mysqld --basedir=/usr |
| Configuração | /etc/mysql/ | /etc/my.cnf e /etc/my.cnf.d/ |
Isolar antes: porque é que este passo não é opcional
Com --skip-grant-tables, o servidor não carrega as tabelas de privilégios. Isso não significa "o root entra sem palavra-passe", significa "qualquer pessoa pode fazer tudo, sem palavra-passe, como qualquer utilizador". Neste estado não existe autenticação nem verificação de privilégios, também não para as bases de dados dos seus clientes.
Neste caso, o MySQL 8 ativa automaticamente também o skip_networking e deixa portanto de aceitar ligações TCP. Ainda assim, não confie nisso e acrescente sempre a opção de forma explícita. No MariaDB essa é de qualquer modo a recomendação documentada, e quem administra os dois sistemas não quer ter de decorar qual deles é que pensa por si.
--skip-grant-tables --skip-networking
Há dois pontos que passam facilmente despercebidos. Primeiro: o skip_networking fecha apenas a porta de rede. O socket Unix em /run/mysqld/mysqld.sock continua aberto e, em muitos sistemas, está acessível a todos os utilizadores locais. Um processo PHP comprometido a correr como www-data consegue, durante essa janela, ler todas as bases de dados do servidor. Mantenha por isso a janela o mais curta possível e não execute esta operação enquanto estiver a correr um servidor web com código desconhecido. Segundo: pare antes tudo o que se liga automaticamente, ou seja, servidores web e serviços aplicacionais. As tentativas de ligação desses serviços não só incomodam como, neste estado, correm com privilégios totais.
Se o servidor estiver acessível a partir do exterior, feche adicionalmente a firewall. Como montar isso de forma limpa e permanente está descrito em configurar a firewall UFW.
sudo apt-get install -y ufw
sudo ufw deny 3306/tcp
sudo ss -ltnp | grep 3306
A primeira linha não está a mais: numa instalação mínima do Debian o ufw não vem pré-instalado, e sem ela o comando termina com sudo: ufw: command not found. No AlmaLinux, no Rocky Linux e no RHEL o ufw nem sequer existe, e aí a filtragem de pacotes passa pelo firewalld:
sudo firewall-cmd --permanent --remove-service=mysql
sudo firewall-cmd --reload
Em todas as instalações Debian e Ubuntu testadas, o bind-address está de qualquer forma em 127.0.0.1, confirmado com ss -ltnp. Nesse caso o servidor já não aceita ligações do exterior, e a regra de firewall é a segunda barreira, não a proteção propriamente dita.
MariaDB: repor a palavra-passe
A unidade do MariaDB arranca o servidor com ExecStart=/usr/sbin/mariadbd $MYSQLD_OPTS. Esta variável existe precisamente para casos destes, pelo que não precisa de editar ficheiro nenhum. Confirme rapidamente e fica a saber que o caminho seguinte funciona no seu sistema:
systemctl cat mariadb | grep ExecStart
Depois, por esta ordem:
sudo systemctl stop mariadb
sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
sudo systemctl start mariadb
sudo mariadb -u root
No prompt, vem primeiro o FLUSH PRIVILEGES. Sem este passo o servidor ainda não tem sequer as tabelas de privilégios em memória e recusa qualquer gestão de contas. Depois define a palavra-passe:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('ASuaNovaPalavraPasse');
Esta escrita um pouco pesada é intencional e é o ponto específico do MariaDB mais importante de todo o guia. Desde a versão 10.4, o MariaDB consegue manter vários métodos de autenticação por conta, e é exatamente assim que o root fica configurado depois de uma instalação por pacote: primeiro o socket, em alternativa a palavra-passe. Se usar em vez disso o simples ALTER USER 'root'@'localhost' IDENTIFIED BY '...', substitui toda a cadeia por autenticação exclusivamente por palavra-passe. Funciona, mas a partir daí o sudo mariadb deixa de entrar sem palavra-passe, e os scripts internos de manutenção da distribuição, que contam com o socket, ficam sem efeito. Nesse caso apenas adiou o problema por um ano.
No fim, desfaça o estado de exceção:
sudo systemctl stop mariadb
sudo systemctl unset-environment MYSQLD_OPTS
sudo systemctl start mariadb
Não se esqueça do unset-environment. A variável fica agarrada ao gestor do systemd, não ao serviço, e sobrevive a qualquer reinício do serviço. Se durante a noite uma atualização de pacotes reiniciar o MariaDB, a sua base de dados passa a correr sem qualquer verificação de privilégios e ninguém dá por isso. Só um reinício do servidor limpa a variável por si.
MySQL 8: o comando da maioria dos guias não faz nada aqui
Para o MySQL circula a mesma receita com systemctl set-environment MYSQLD_OPTS=.... Vem da documentação da Oracle e encaixa nos pacotes da própria Oracle. Só que a unidade do arquivo do Ubuntu é diferente: aí está simplesmente ExecStart=/usr/sbin/mysqld, sem variável. O comando corre até ao fim, não devolve erro nenhum, e mesmo assim o servidor arranca perfeitamente normal, com a verificação de privilégios ativa. Fica então a olhar para um Access denied sem perceber porquê. Verifique por si mesmo:
systemctl cat mysql | grep ExecStart
Se aí não aparecer nenhum $MYSQLD_OPTS, precisa de um drop-in. E já que vai criar um, escolha logo o caminho melhor: --init-file. Com ele, o servidor arranca de forma perfeitamente normal com verificação de privilégios e executa no arranque um ficheiro SQL com privilégios totais. Não existe qualquer janela aberta em que alguém possa entrar sem palavra-passe. A Oracle recomenda expressamente esta variante em vez do --skip-grant-tables.
sudo systemctl stop mysql
printf "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'ASuaNovaPalavraPasse';\n" | sudo tee /var/lib/mysql-files/kh-reset.sql
sudo chown mysql:mysql /var/lib/mysql-files/kh-reset.sql
sudo chmod 600 /var/lib/mysql-files/kh-reset.sql
sudo mkdir -p /etc/systemd/system/mysql.service.d
O IDENTIFIED WITH caching_sha2_password é a parte decisiva e a razão pela qual inúmeros guias falham neste ponto sem mostrar erro nenhum. No Debian e no Ubuntu, o root@localhost usa o plugin auth_socket também no MySQL. Um simples IDENTIFIED BY 'passwort' define de facto o hash da palavra-passe, mas não troca o plugin. Em mysql.user continua a constar auth_socket, o serviço arranca sem problemas, não aparece uma única mensagem de erro, e mesmo assim qualquer início de sessão por palavra-passe termina com ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Particularmente traiçoeiro: quem faz a contraprova como utilizador de sistema root é deixado passar pelo auth_socket sem palavra-passe e fica convencido de que a reposição correu bem. Teste por isso a partir de outra conta ou através de TCP. Só com clientes muito antigos, que não dominam o caching_sha2_password, é que deve usar em alternativa o mysql_native_password, com as limitações descritas no parágrafo mais abaixo.
O diretório /var/lib/mysql-files foi escolhido de propósito: pertence ao utilizador da base de dados e está autorizado no perfil AppArmor do mysqld. Se colocar o ficheiro em /root ou /tmp, o arranque pode falhar por causa do AppArmor, e a mensagem no log ajuda muito pouco. Agora o drop-in:
[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --init-file=/var/lib/mysql-files/kh-reset.sql
A primeira linha ExecStart vazia é obrigatória, caso contrário o systemd acrescenta o seu comando ao que já existe e recusa o serviço com um erro de configuração. Guarde em /etc/systemd/system/mysql.service.d/override.conf e depois:
sudo systemctl daemon-reload
sudo systemctl start mysql
sudo mysql -u root -p
Se o início de sessão funcionar, remova as duas coisas, o ficheiro SQL e o drop-in:
sudo rm -f /var/lib/mysql-files/kh-reset.sql
sudo rm -f /etc/systemd/system/mysql.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart mysql
Se preferir mesmo assim o caminho clássico, substitua no drop-in essa linha por ExecStart=/usr/sbin/mysqld --skip-grant-tables --skip-networking, ligue-se com sudo mysql e execute aí primeiro FLUSH PRIVILEGES; e só depois o ALTER USER. Uma nota sobre a cifragem: o MySQL 8 usa por omissão o caching_sha2_password. Se, depois disso, uma aplicação muito antiga responder com "The server requested authentication method unknown to the client", a solução passa por IDENTIFIED WITH mysql_native_password BY '...'. Isso é, no entanto, um beco sem saída, porque este método está marcado como obsoleto desde a versão 8.0.34 e já não existe no MySQL 8.4. O melhor é atualizar o cliente.
Mensagens de erro na íntegra
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement. Esqueceu-se do FLUSH PRIVILEGES;. Execute-o e a seguir o ALTER USER funciona.
ERROR 1288 (HY000): The target table user of the UPDATE is not updatable. Está a seguir um guia antigo que sugere UPDATE mysql.user SET password=.... A partir do MariaDB 10.4 os privilégios estão em mysql.global_priv, e mysql.user passou a ser apenas uma vista sobre essa tabela. Use ALTER USER ou SET PASSWORD. A propósito: escrever diretamente nas tabelas de privilégios já antes era um bom método para destruir a conta de vez.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Não é palavra-passe errada, é o contrário: a conta espera autenticação por socket, e ou não está a trabalhar como utilizador de sistema root ou passou o -p. Tente de novo com sudo e sem -p.
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded. A conta aponta para um plugin que o servidor em execução não conhece, o que é típico depois de uma mudança de MariaDB para MySQL ou depois de copiar um diretório de dados. Passe a conta para um método adequado pelo caminho descrito acima.
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock' (2). O servidor não está a correr. Veja primeiro o systemctl status e o log, em vez de continuar a tentar do lado do cliente.
mysqld: Can't create directory '/run/mysqld/' (Errcode: 13 - Permission denied) ou um arranque que aborta logo a seguir. Isto acontece quando o servidor foi iniciado à mão em vez de pelo systemd, porque nesse caso falta o diretório de tempo de execução. Reparação:
sudo mkdir -p /run/mysqld
sudo chown mysql:mysql /run/mysqld
Job for mysql.service failed because the control process exited with error code. Esta mensagem, por si só, não diz nada. A causa real está no journal e no log de erros:
sudo journalctl -u 'mysql*' -u 'mariadb*' -n 60 --no-pager
O padrão com os dois nomes de unidade foi escolhido de propósito, porque a consulta individual mais óbvia leva a uma armadilha silenciosa. No Debian com MariaDB, mysql.service é apenas um alias de mariadb.service. O systemctl resolve o alias, mas o journal indexa as entradas sob o nome real da unidade, e por isso o journalctl -u mysql responde com -- No entries -- e código de saída 0, apesar de o journal estar cheio. Ao contrário, o journalctl -u mariadb num sistema Ubuntu com MySQL devolve igualmente -- No entries --.
Passa-se algo semelhante com o ficheiro de erros. O /var/log/mysql/error.log citado por toda a parte só existe no Ubuntu com MySQL. No Debian com MariaDB o diretório /var/log/mysql/ nem sequer existe, porque o log_error está comentado no 50-server.cnf e o MariaDB escreve no journal. A linha adequada consoante o sistema:
| Sistema e servidor | Comando |
|---|---|
| Debian ou Ubuntu, MariaDB | sudo journalctl -u mariadb -n 60 --no-pager |
| Ubuntu, MySQL | sudo tail -n 60 /var/log/mysql/error.log |
| AlmaLinux, Rocky, RHEL, MySQL | sudo tail -n 60 /var/log/mysql/mysqld.log |
| AlmaLinux, Rocky, RHEL, MariaDB | sudo tail -n 60 /var/log/mariadb/mariadb.log |
Se não quiser adivinhar o caminho, pergunte ao próprio servidor: sudo mariadb -e "SHOW VARIABLES LIKE 'log_error';" ou o mesmo com mysql.
As três causas mais frequentes neste ponto: um erro de escrita no drop-in (nesse caso o systemd indica a linha), um disco cheio (sobre isso, disco cheio no Linux) ou um segundo processo do servidor que ainda está a correr e mantém os ficheiros de dados bloqueados. Este último verifica-se com pgrep -a mariadbd ou pgrep -a mysqld, antes de voltar a arrancar.
Se depois da reposição consegue entrar mas as suas aplicações continuam a ser recusadas, o problema está noutro sítio: aplicações como o WordPress ou o Nextcloud usam utilizadores de base de dados próprios, não o root. Mais pormenores em resolver o MySQL Access denied for user.
Como saber que resultou mesmo
Um início de sessão bem-sucedido não chega, por si só, como prova, porque no estado de emergência também funciona sem palavra-passe nenhuma. Verifique por isso quatro coisas depois de o serviço voltar a correr normalmente.
Primeiro: já não existe configuração especial nenhuma. A primeira saída tem de estar vazia, e a segunda não pode mostrar mais nenhuma opção adicional.
systemctl show-environment | grep MYSQLD_OPTS
systemctl cat mariadb | grep ExecStart
Segundo: a verificação de privilégios está outra vez ativa. Um início de sessão com uma palavra-passe deliberadamente errada tem de falhar. Se passar, o servidor continua aberto.
Terceiro: a nova palavra-passe e o método esperado estão registados na conta. No MariaDB consulta-se assim, no MySQL sem a coluna JSON:
sudo mariadb -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"
sudo mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"
Quarto: a porta de rede volta a comportar-se como antes. Um servidor de base de dados usado apenas localmente deve, depois da limpeza, voltar a escutar exclusivamente em 127.0.0.1:
sudo ss -ltnp | grep 3306
grep -rs bind-address /etc/mysql/ /etc/my.cnf /etc/my.cnf.d/
O -s e os três caminhos são intencionais. O grep -r bind-address /etc/mysql/ sozinho aborta na família Red Hat com No such file or directory, porque aí não existe /etc/mysql/. Com -s, o grep ignora em silêncio os caminhos inexistentes, e a linha serve para os dois mundos.
Arrumar, para não acontecer uma segunda vez
A palavra-passe está agora possivelmente em sítios em que não pensa. Um comando com -e "ALTER USER ... IDENTIFIED BY '...'" vai parar ao ~/.bash_history, e um comando escrito no prompt vai para o ficheiro de histórico do cliente da base de dados. Convém limpar os dois:
history -c
rm -f ~/.mysql_history ~/.mariadb_history
Os dois nomes de ficheiro são necessários porque o cliente mudou de nome. Até ao MariaDB 10.11, ou seja, até ao Debian 12 inclusive, escreve em ~/.mysql_history. A partir do MariaDB 11, ou seja, a partir do Debian 13, escreve em ~/.mariadb_history. Quem apagar aí apenas o ficheiro antigo deixa no disco, sem dar por isso, a palavra-passe escrita em texto simples. Melhor ainda é não deixar sequer que o histórico chegue a existir: export MYSQL_HISTFILE=/dev/null ou export MARIADB_HISTFILE=/dev/null antes da sessão, ou então logo o caminho por --init-file da secção do MySQL, no qual a palavra-passe nunca passa por uma sessão interativa.
Mais sensato do que uma palavra-passe que voltará a esquecer daqui a um ano é uma configuração sem palavra-passe no dia a dia. No Debian e no Ubuntu isso significa: o root mantém-se em unix_socket ou auth_socket, e para tudo o resto cria utilizadores normais com exatamente os privilégios de que cada aplicação precisa. Quem precisar de acesso a partir de outro computador faz um túnel por SSH em vez de abrir a porta 3306, veja ligar ao servidor por SSH.
Já que está a trabalhar na base de dados, aproveite para tratar do resto: remover os utilizadores anónimos, apagar a base de dados de teste, desativar o acesso remoto do root. O mariadb-secure-installation, ou o mysql_secure_installation, trata disso em poucos minutos, e está descrito em pormenor em proteger o MariaDB e o MySQL. E como a palavra-passe raramente é a única coisa mal resolvida num servidor acabado de assumir, vale a pena passar pela lista de verificação para servidores root novos.
Uma última ideia sobre a ordem dos passos: antes de colocar um servidor de produção em funcionamento no estado sem privilégios, faça um backup do diretório de dados ou um snapshot. A reposição em si é inofensiva, mas um serviço que deixa de arrancar por causa de um erro de escrita na unidade já não é inofensivo às três da manhã.
Perguntas frequentes
Esqueci-me da palavra-passe root. Tenho mesmo de parar o MySQL?
Porque é que recebo o ERROR 1290 apesar de ter arrancado com skip-grant-tables?
O meu servidor fica vulnerável durante a reposição?
Qual é a diferença entre o MySQL 8 e o MariaDB na reposição?
Depois da reposição entro como root, mas o meu site continua a devolver Access denied. Porquê?
O que acontece se me esquecer do systemctl unset-environment?
2026 KernelHost GmbH. Todos os direitos reservados. Este guia está protegido por direitos de autor. A sua republicação noutros sites, na íntegra, em parte ou de forma editada, não é permitida sem o nosso consentimento por escrito. Citações com indicação da fonte e ligação são expressamente bem-vindas.

