Repor a palavra-passe root do MySQL e do MariaDB

Publicado a 17 min de leitura

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:

Sistemamysql-servermariadb-server
Debian 13não existe11.8
Debian 12não existe10.11
Ubuntu 24.048.010.11
Ubuntu 22.048.010.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 UbuntuAlmaLinux, Rocky, RHEL
Unidade MariaDBmariadb.servicemariadb.service
Unidade MySQLmysql.servicemysqld.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 servidorComando
Debian ou Ubuntu, MariaDBsudo journalctl -u mariadb -n 60 --no-pager
Ubuntu, MySQLsudo tail -n 60 /var/log/mysql/error.log
AlmaLinux, Rocky, RHEL, MySQLsudo tail -n 60 /var/log/mysql/mysqld.log
AlmaLinux, Rocky, RHEL, MariaDBsudo 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?
Na maioria dos casos, não. No Debian e no Ubuntu, o root da base de dados está protegido pelo socket Unix e não por uma palavra-passe. Experimente primeiro sudo mariadb ou sudo mysql, sempre sem a opção -p. Se resultar, define a nova palavra-passe com ALTER USER com o serviço a correr. Só quando este caminho falha com Access denied é que precisa do reinício com a verificação de privilégios desativada.
Porque é que recebo o ERROR 1290 apesar de ter arrancado com skip-grant-tables?
Porque neste estado o servidor não carregou as tabelas de privilégios e por isso não consegue executar qualquer gestão de contas. Execute primeiro FLUSH PRIVILEGES no prompt e a seguir o ALTER USER funciona como sempre. A mensagem completa é: The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement.
O meu servidor fica vulnerável durante a reposição?
Fica, e por completo. Com a verificação de privilégios desativada deixa de existir autenticação, e qualquer tentativa de ligação tem todos os privilégios sobre todas as bases de dados. O MySQL 8 desliga automaticamente a porta de rede, o MariaDB não necessariamente. Acrescente por isso sempre o --skip-networking de forma explícita, pare os servidores web e os serviços aplicacionais, e limite a janela a poucos minutos. Ainda assim, o socket local continua aberto a todos os utilizadores do sistema.
Qual é a diferença entre o MySQL 8 e o MariaDB na reposição?
São três coisas. Primeiro, a unidade do systemd: no MariaDB funciona o systemctl set-environment MYSQLD_OPTS, no MySQL do arquivo do Ubuntu não, e aí precisa de um drop-in com uma linha ExecStart própria. Segundo, as tabelas de privilégios: a partir do MariaDB 10.4, mysql.user passou a ser apenas uma vista sobre mysql.global_priv, e os comandos UPDATE diretos falham com ERROR 1288. Terceiro, a sintaxe da conta: o MariaDB consegue manter os dois métodos ao mesmo tempo com IDENTIFIED VIA unix_socket OR mysql_native_password, ao passo que o MySQL só conhece um método por conta. No MySQL, o WITH é decisivo: ALTER USER ... IDENTIFIED BY define apenas o hash da palavra-passe e deixa o plugin em auth_socket, pelo que o início de sessão por palavra-passe continua a falhar com ERROR 1698. O correto é ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '...'.
Depois da reposição entro como root, mas o meu site continua a devolver Access denied. Porquê?
Porque aplicações como o WordPress, o Nextcloud ou as lojas online não trabalham com a conta root, mas sim com utilizadores de base de dados próprios. As palavras-passe desses utilizadores estão no ficheiro de configuração de cada aplicação e não são afetadas pela reposição do root. Verifique o utilizador indicado na aplicação e, se for necessário, defina a palavra-passe dele em conformidade.
O que acontece se me esquecer do systemctl unset-environment?
A variável fica agarrada ao gestor do systemd e sobrevive a qualquer reinício do serviço. Se durante a noite uma atualização de pacotes reiniciar a base de dados, esta passa a partir desse momento a correr permanentemente sem verificação de privilégios, sem que ninguém repare. Só um reinício de todo o servidor a limpa por si. Depois do trabalho feito, confirme com systemctl show-environment que MYSQLD_OPTS já não está definido.

MySQL MariaDB Palavra-passe root Debian Ubuntu systemd Base de dados Administração de servidores unix_socket Resolução de problemas