Como esconder a versão WordPress e proteger cabeçalhos HTTP (guia 2026)

Como esconder a versão WordPress e proteger cabeçalhos HTTP (guia 2026)

Última verificação: 15 de outubro de 2026
19 min de leitura
Guia
Na arquitetura de segurança defensiva na web, a fase de reconhecimento (reconnaissance) representa o primeiro passo de qualquer agente malicioso. Ferramentas automatizadas e botnets realizam varreduras contínuas à procura de versões desatualizadas do WordPress ou de configurações padrão vulneráveis. Se uma aplicação web anuncia de imediato qual o número de versão exato que está a executar, entra diretamente no radar de ataques dirigidos.

Ocultar o número da versão do WordPress encaixa-se no princípio de “segurança por obscuridade”. Isoladamente, esta medida não substitui a manutenção de rotina nem a aplicação atempada de patches de segurança. No entanto, quando aliada à implementação rigorosa de cabeçalhos HTTP de segurança (Security Headers), ao encerramento de interfaces herdadas como o XML-RPC e à restrição de acessos no sistema de ficheiros, constitui uma barreira eficaz de redução de ruído e mitigação de exploração em massa.

Neste guia técnico, abordamos o hardening de uma plataforma WordPress, cobrindo o ciclo de vida dos pedidos HTTP e a configuração dos servidores web Apache e Nginx.

#Como o WordPress expõe a sua versão ao público

Por omissão, uma instalação de WordPress disponibiliza pistas explícitas sobre a sua versão em várias camadas da resposta HTTP e do código-fonte HTML:

  1. Meta tag generator no cabeçalho HTML:
    <meta name="generator" content="WordPress 6.7.1" />
    Esta etiqueta é injetada automaticamente pela ação interna wp_head e constitui o indicador mais comum recolhido por scanners de vulnerabilidades.
  2. Feeds de sindicação de conteúdos (RSS, Atom, RDF): Os ficheiros de feed XML gerados em /feed/ contêm elementos dedicados como <generator>https://wordpress.org/?v=6.7.1</generator>.
  3. Query strings em recursos estáticos (CSS e JS): Ao carregar scripts do núcleo do WordPress, como folhas de estilo do bloco Gutenberg ou ficheiros de suporte a emojis, os URLs incluem parâmetros explícitos, por exemplo ?ver=6.7.1.
  4. Ficheiros estáticos da raiz da instalação: Ficheiros como readme.html ou license.txt permanecem frequentemente acessíveis publicamente na raiz do servidor web, revelando notas de lançamento da versão instalada.

A remoção estrutural destes elementos impede que scanners em lote associem a sua instalação a tabelas públicas de CVEs (Common Vulnerabilities and Exposures) antes mesmo de analisarem comportamentos específicos do sistema.

#Como remover a versão do WordPress em PHP

Muitos administradores aplicam pequenos excertos de código encontrados na web que removem indiscriminadamente qualquer parâmetro ver de todas as folhas de estilo e ficheiros JavaScript. Essa prática acarreta um problema operacional grave: anula o mecanismo de cache busting do browser e das redes de distribuição de conteúdo (CDNs). Quando um tema ou plugin é atualizado, os utilizadores continuam a receber ficheiros estáticos antigos em cache porque o URL do recurso não mudou.

A abordagem de engenharia correta consiste em remover apenas os valores que correspondem à versão principal do núcleo do WordPress, preservando versões personalizadas e hashes de ficheiros de terceiros.

#Remover a meta tag generator do head e dos feeds

A forma mais limpa e manutenível de aplicar estas regras é através de um Must-Use Plugin (wp-content/mu-plugins/security-hardening.php), garantindo que a execução não depende do tema ativo:

<?php
/**
 * Plugin Name: Hardening de Reconhecimento WordPress
 * Description: Remove a identificação pública da versão do núcleo nos cabeçalhos HTML e feeds.
 */

// 1. Remover a meta tag generator do HTML head
remove_action('wp_head', 'wp_generator');

// 2. Remover identificadores de versão em feeds RSS e Atom
remove_action('rss_head', 'the_generator');
remove_action('rss2_head', 'the_generator');
remove_action('commentsrss2_head', 'the_generator');
remove_action('atom_head', 'the_generator');
remove_action('rdf_header', 'the_generator');
remove_action('opml_head', 'the_generator');

// 3. Limpar saídas textuais do filtro the_generator
add_filter('the_generator', '__return_empty_string');

#Remover o parâmetro ?ver= de scripts e estilos de forma seletiva

Para tratar os URLs dos ficheiros estáticos sem prejudicar as políticas de cache dos plugins, implemento um filtro que valida se o argumento ver é idêntico à variável global $wp_version:

function wppoland_remover_versao_core_assets(string $src): string {
    global $wp_version;

    if (empty($src)) {
        return $src;
    }

    $url_decomposta = parse_url($src);
    if (!isset($url_decomposta['query'])) {
        return $src;
    }

    parse_str($url_decomposta['query'], $parametros);

    // Remove apenas se o valor da query string for exatamente a versão do núcleo
    if (isset($parametros['ver']) && $parametros['ver'] === $wp_version) {
        return remove_query_arg('ver', $src);
    }

    return $src;
}
add_filter('style_loader_src', 'wppoland_remover_versao_core_assets', 9999);
add_filter('script_loader_src', 'wppoland_remover_versao_core_assets', 9999);

Com este filtro ativo, os ficheiros nativos do WordPress perdem a referência pública de versão, enquanto scripts com versões semânticas independentes continuam a beneficiar de invalidação atempada de cache no browser do utilizador.

#Que cabeçalhos HTTP revelam o WordPress além da meta tag generator

A meta tag generator não é a única impressão digital. O núcleo e o próprio PHP acrescentam cabeçalhos de resposta que dizem a um scanner qual o software do site, mesmo quando nenhum número de versão aparece no HTML.

CabeçalhoQuem o enviaQuando
Link: <https://exemplo.pt/wp-json/>; rel="https://api.w.org/"rest_output_link_header(), desde o WordPress 4.4.0em cada pedido ao front end, na ação template_redirect (prioridade 11)
Link: <https://exemplo.pt/?p=123>; rel=shortlinkwp_shortlink_header()em artigos e páginas individuais, na ação template_redirect (prioridade 11)
X-Pingback: https://exemplo.pt/xmlrpc.phpWP::send_headers()em artigos individuais que aceitam pings (pings_open()), de forma condicional desde a 4.4.0
X-Powered-By: PHP/8.3.xo próprio PHPenquanto a diretiva expose_php estiver ativa, o que é o valor por omissão

Fontes: a referência de rest_output_link_header, a referência de WP::send_headers e a documentação das diretivas do núcleo do PHP.

Os cabeçalhos do WordPress saem com os mesmos hooks que os acrescentam. O X-Pingback passa pelo filtro wp_headers, que filtra os cabeçalhos HTTP antes de serem enviados ao browser. Acrescente ao mesmo mu-plugin:

// Descoberta da REST API: cabeçalho Link e etiqueta <link>
remove_action('template_redirect', 'rest_output_link_header', 11);
remove_action('wp_head', 'rest_output_link_wp_head', 10);

// Shortlink: cabeçalho e etiqueta <link>
remove_action('template_redirect', 'wp_shortlink_header', 11);
remove_action('wp_head', 'wp_shortlink_wp_head', 10);

// X-Pingback em artigos individuais
add_filter('wp_headers', function (array $headers): array {
    unset($headers['X-Pingback']);
    return $headers;
});

Retirar a ligação de descoberta não desliga a REST API: o endereço /wp-json/ continua a responder e o editor de blocos depende dele. O objetivo é deixar de anunciar o CMS em cada resposta HTML, não partir a API.

O X-Powered-By não se altera a partir do WordPress. O manual do PHP indica que expose_php só pode ser definido no php.ini, por isso ini_set() não tem efeito. Defina-o no pool do PHP-FPM ou no php.ini principal:

expose_php = Off

O OWASP HTTP Headers Cheat Sheet recomenda remover todos os cabeçalhos X-Powered-By e retirar o cabeçalho Server ou dar-lhe um valor sem informação. O mesmo documento lembra que um atacante consegue identificar a pilha tecnológica por outros meios, e esse é o limite honesto de toda esta secção.

#Cabeçalhos HTTP de segurança no WordPress

Os cabeçalhos HTTP de segurança são diretivas enviadas pelo servidor que instruem os navegadores modernos sobre como lidar com o isolamento de recursos, prevenção de carregamento em molduras externas e políticas de execução de scripts.

A configuração direta no servidor web (Apache ou Nginx) é altamente preferível à execução via PHP, uma vez que servidores proxy reversos e sistemas de cache em página inteira (Full Page Cache) entregam respostas estáticas sem invocar o motor PHP.

#1. X-Content-Type-Options: nosniff

Impede que o navegador tente adivinhar o tipo MIME de um ficheiro quando este difere do cabeçalho Content-Type declarado pelo servidor. Isso anula ataques onde um invasor carrega uma imagem contendo código JavaScript oculto e induz o navegador a executá-la.

Header always set X-Content-Type-Options "nosniff"

#2. X-Frame-Options: SAMEORIGIN

Protege a aplicação contra ataques de Clickjacking. Se esta diretiva não estiver presente, uma página web maliciosa externa pode incorporar o painel administrativo ou o formulário de checkout da sua loja num elemento <iframe> invisível, induzindo o utilizador autenticado a executar ações destrutivas.

Header always set X-Frame-Options "SAMEORIGIN"

#3. HTTP Strict Transport Security (HSTS)

O HSTS força o browser a comunicar exclusivamente através do protocolo HTTPS, eliminando tentativas de downgrade para HTTP inseguro em redes públicas ou ataques de Man-in-the-Middle (MitM).

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

O parâmetro max-age=31536000 define a validade da política por um período de um ano civil (em segundos).

#4. Referrer-Policy: strict-origin-when-cross-origin

Regula a quantidade de dados de encaminhamento (Referer) transmitidos para websites externos quando os visitantes clicam em hiperligações. Esta política envia o caminho completo em pedidos internos seguros, mas reduz os dados apenas ao nome de domínio de origem em pedidos entre domínios diferentes sob HTTPS.

Header always set Referrer-Policy "strict-origin-when-cross-origin"

#5. Permissions-Policy

Permite desativar recursos do hardware do cliente (como câmara, microfone ou geolocalização) que o seu website institucional ou de comércio eletrónico não utiliza. Se uma biblioteca de terceiros for comprometida, o navegador bloqueia qualquer tentativa de ativação desses sensores.

Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"

#6. Content-Security-Policy (CSP)

A Content Security Policy é a salvaguarda mais robusta contra ataques de Cross-Site Scripting (XSS). Ao declarar expressamente os domínios autorizados a servir scripts, estilos, fontes e conexões assíncronas, inviabiliza a execução de código arbitrário injetado na base de dados.

Um exemplo prático e equilibrado para sites WordPress com Google Analytics e bibliotecas externas autorizadas:

Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com; frame-ancestors 'self';"

#7. X-XSS-Protection: ainda faz sentido em 2026

Muitos guias antigos recomendavam X-XSS-Protection: 1; mode=block. Esse conselho está desatualizado. A página do MDN sobre X-XSS-Protection classifica o cabeçalho como obsoleto e não normalizado, recomenda uma Content-Security-Policy em seu lugar e avisa que, em alguns casos, o filtro pode criar vulnerabilidades XSS em sites que de outra forma seriam seguros. O MDN acrescenta que mode=block pode ficar exposto a ataques de canal lateral quando a página pode ser incorporada num <iframe>.

A OWASP dá a mesma resposta: não enviar o cabeçalho ou defini-lo explicitamente como 0. Se uma configuração antiga ou um plugin já envia 1; mode=block, substitua o valor:

Header always set X-XSS-Protection "0"
add_header X-XSS-Protection "0" always;

A proteção que realmente interessa vem de uma CSP que não permita scripts 'unsafe-inline', o que na maioria dos sites WordPress é um projeto mais longo.

#O WordPress envia X-Frame-Options sozinho

Sim, em dois ecrãs. O núcleo liga a função send_frame_options_header() à ação login_init e, no ficheiro wp-admin/includes/admin-filters.php, à ação admin_init. A função, disponível desde o WordPress 3.1.3, envia dois cabeçalhos quando ainda nenhum foi enviado:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';

Fonte: a referência de send_frame_options_header.

O wp-login.php e o painel ficam, portanto, protegidos contra molduras mesmo sem configuração no servidor. O front end público não: temas, páginas de checkout e formulários de contacto não recebem este cabeçalho se não o acrescentar.

Isto tem uma consequência prática no Apache. A documentação do mod_headers explica que set substitui qualquer cabeçalho anterior com o mesmo nome, enquanto append junta o novo valor ao existente, separado por vírgula. Com Header append, a página de login leva o valor do WordPress e o seu. Com Header always set, leva exatamente um. A mesma documentação explica que os cabeçalhos produzidos pelo PHP atrás do mod_proxy_fcgi ficam na tabela always, e é por isso que os exemplos deste guia usam sempre Header always set. O MDN lembra ainda que ALLOW-FROM é uma diretiva obsoleta que leva os browsers modernos a ignorar o cabeçalho inteiro, e que X-Frame-Options definido numa etiqueta <meta> não tem qualquer efeito.

#Configuração Apache e Nginx para esconder a versão WordPress

Abaixo encontram-se exemplos prontos para utilização nos ambientes de alojamento mais comuns.

#Configuração Apache (.htaccess)

Coloque estas regras no topo do seu ficheiro .htaccess, antes do bloco padrão # BEGIN WordPress:

# --- WPPoland Hardening de Cabeçalhos HTTP ---
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    # Cabeçalho obsoleto: desligado de forma explícita (ver secção sobre X-XSS-Protection)
    Header always set X-XSS-Protection "0"
</IfModule>

# Bloquear ficheiros de documentação e ficheiros sensíveis
<FilesMatch "^(wp-config\.php|readme\.html|license\.txt|\.env)">
    Require all denied
</FilesMatch>

# Desativar acesso direto ao XML-RPC
<Files xmlrpc.php>
    Require all denied
</Files>

#Configuração do bloco server no Nginx

No Nginx, declare os cabeçalhos dentro do bloco de servidor principal correspondente a HTTPS:

server {
    listen 443 ssl http2;
    server_name exemplo.pt www.exemplo.pt;

    ssl_certificate /etc/letsencrypt/live/exemplo.pt/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exemplo.pt/privkey.pem;

    # Cabeçalhos de Segurança
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Ocultar versão do servidor Nginx
    server_tokens off;

    root /var/www/exemplo.pt;
    index index.php index.html;

    # Bloqueio de ficheiros do núcleo informativos
    location ~* /(?:readme\.html|license\.txt|wp-config\.php) {
        deny all;
    }

    # Bloqueio total a pedidos XML-RPC
    location = /xmlrpc.php {
        deny all;
        access_log off;
        log_not_found off;
    }

    # Processamento PHP FastCGI
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

#Porque desaparecem os cabeçalhos de segurança nos blocos location do Nginx

Uma surpresa comum depois de uma configuração “que funciona”: curl -I na página inicial mostra todos os cabeçalhos de segurança, mas um ficheiro CSS ou o wp-login.php não mostra nenhum. A documentação do módulo ngx_http_headers_module explica porquê: as diretivas add_header são herdadas do nível de configuração anterior se, e só se, não existir nenhuma diretiva add_header no nível atual.

Basta um add_header Cache-Control ... dentro de location ~* \.(css|js)$ para que esse location perca todos os cabeçalhos de segurança. Há duas saídas:

  1. Guardar os cabeçalhos num snippet e incluí-lo no bloco server e em cada location que defina o seu próprio add_header:
# /etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
location ~* \.(css|js|woff2)$ {
    include snippets/security-headers.conf;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
  1. No Nginx 1.29.3 ou mais recente, a diretiva add_header_inherit merge; junta os valores do nível anterior aos do nível atual em vez de os substituir.

O parâmetro always também conta. Sem ele, o Nginx só acrescenta o cabeçalho a respostas com os códigos 200, 201, 204, 206, 301, 302, 303, 304, 307 ou 308. Um 403 no /xmlrpc.php ou uma página 404 saem sem proteção.

#Requisitos da lista HSTS preload

O token preload nos exemplos acima não produz efeito sozinho. É um pedido para que o domínio fique gravado no código dos browsers, e o site hstspreload.org enumera as condições de submissão:

  • servir um certificado válido,
  • redirecionar de HTTP para HTTPS no mesmo host, se o servidor escutar na porta 80,
  • servir todos os subdomínios por HTTPS, incluindo o www se existir um registo DNS para ele,
  • enviar o cabeçalho HSTS no domínio base, por HTTPS, com max-age de pelo menos 31536000 segundos, includeSubDomains e preload,
  • se o site HTTPS fizer mais um redirecionamento, esse redirecionamento tem de levar o cabeçalho HSTS.

A mesma página avisa que o preload se aplica a todos os subdomínios, incluindo os internos que não são públicos, que as novas entradas podem demorar vários meses a chegar à versão estável do Chrome e que a remoção da lista costuma ser lenta e penosa. Um ambiente de staging num subdomínio sem certificado deixa de abrir em qualquer browser que tenha a lista.

O valor recomendado pela OWASP é max-age=63072000; includeSubDomains; preload, ou seja, dois anos. A ordem que evita bloqueios: começar com um max-age curto e sem preload, verificar todos os subdomínios, aumentar o valor e submeter o domínio apenas quando todo ele for exclusivamente HTTPS de forma definitiva. Se ainda não está pronto para esse compromisso, retire preload do cabeçalho.

#Como bloquear XML-RPC e a enumeração de utilizadores

A robustez de um sistema WordPress passa pela redução deliberada da superfície exposta. Existem dois vetores clássicos de recolha de credenciais que devem ser neutralizados em qualquer instalação corporativa:

#Bloqueio do ficheiro xmlrpc.php

O protocolo XML-RPC foi projetado para comunicações remotas antes da estabilização da REST API moderna. Atualmente, representa um risco operacional grave devido ao método system.multicall. Com uma única requisição HTTP POST contendo um payload XML estruturado, um invasor pode submeter centenas de tentativas de autenticação combinadas, contornando plugins convencionais de limitação de tentativas de login.

A menos que utilize aplicações legadas como o módulo de publicação remota do Jetpack ou a aplicação móvel original do WordPress, deve desativar categoricamente o acesso ao ficheiro xmlrpc.php ao nível do servidor web (como demonstrado nas configurações acima).

Não confie no filtro add_filter('xmlrpc_enabled', '__return_false'); como bloqueio total. A referência do hook xmlrpc_enabled explica que, ao contrário do que o nome sugere, o filtro só controla os métodos XML-RPC que exigem autenticação. Os pingbacks e outros endpoints sem login continuam abertos. Só a regra no servidor web trava o pedido antes de o PHP ser executado.

#Prevenção de enumeração de utilizadores na REST API

Por omissão, a interface REST do WordPress expõe o endpoint público /wp-json/wp/v2/users, que lista os autores de artigos com respetivos slugs de utilizador (nomes de conta no login). Isso permite a bots mapear os nomes de utilizador antes de iniciarem ataques de força bruta no ecrã de acesso.

Para mitigar este vetor, intercete os endpoints da API para utilizadores não autorizados. A verificação list_users é importante: sem ela, os editores perdem a lista de autores no editor de blocos, que lê o mesmo endpoint.

add_filter('rest_endpoints', function(array $endpoints): array {
    if (isset($endpoints['/wp/v2/users']) && !current_user_can('list_users')) {
        unset($endpoints['/wp/v2/users']);
    }
    if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)']) && !current_user_can('list_users')) {
        unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
    }
    return $endpoints;
});

Adicionalmente, intercete o parâmetro arcaico de consulta de autores por ID via URL (/?author=1), redirecionando-o para a página inicial:

add_action('template_redirect', function(): void {
    if (is_author() && !is_admin()) {
        wp_safe_redirect(home_url(), 301);
        exit;
    }
});

#Como proteger a página de login do WordPress

Os endpoints /wp-admin/ e /wp-login.php estão sob ataque constante. Eis como protegê-los.

#1. Limitar as tentativas de login

Por omissão, o WordPress permite tentativas de login ilimitadas.

Plugin: Limit Login Attempts Reloaded, ou fail2ban no servidor a ler os logins falhados no access log. Uma versão mínima em mu-plugin tem de se ligar ao filtro authenticate, caso contrário o contador nunca bloqueia nada:

add_filter('authenticate', function ($user) {
    $key = 'wppoland_login_fail_' . md5($_SERVER['REMOTE_ADDR'] ?? '');
    if ((int) get_transient($key) >= 5) {
        return new WP_Error('too_many_attempts', 'Too many failed logins. Try again in 15 minutes.');
    }
    return $user;
}, 30);

add_action('wp_login_failed', function () {
    $key = 'wppoland_login_fail_' . md5($_SERVER['REMOTE_ADDR'] ?? '');
    set_transient($key, (int) get_transient($key) + 1, 15 * MINUTE_IN_SECONDS);
});

Atrás de um reverse proxy ou de uma CDN, REMOTE_ADDR é o endereço do proxy, por isso este código bloquearia toda a gente de uma vez. Configure primeiro o IP real do cliente no servidor web.

#2. Autenticação de dois fatores (2FA)

Mesmo que um atacante obtenha a sua palavra-passe, não consegue entrar sem o segundo fator.

Plugins:

#3. Alterar o URL de login

Por omissão, toda a gente sabe que o login está em /wp-login.php.

Plugin: WPS Hide Login

O plugin muda o URL para algo como /o-meu-login-secreto/. Os bots que procuram /wp-login.php recebem um 404. Isto volta a ser obscuridade: reduz o ruído nos logs, mas não substitui a 2FA. O bloqueio da enumeração de utilizadores está descrito na secção anterior.

#Como proteger a base de dados do WordPress

#Alterar o prefixo das tabelas

Por omissão, o WordPress usa wp_ como prefixo das tabelas. Um prefixo próprio não impede SQL injection; apenas faz falhar scripts automáticos que têm os nomes das tabelas wp_ escritos no código.

Durante a instalação: defina um prefixo próprio, como xyz_ ou prod_.

Depois da instalação: use um plugin como o Change Table Prefix ou mude à mão o nome das tabelas e das chaves com prefixo em options e usermeta (faça antes uma cópia de segurança).

A desativação do editor de ficheiros (DISALLOW_FILE_EDIT) e as permissões do wp-config.php estão na secção seguinte.

#Permissões de ficheiros e diretórios WordPress no Linux

Mesmo com cabeçalhos fortes, o isolamento a nível do sistema operacional é a barreira final de contenção caso uma vulnerabilidade em algum plugin permita o envio de ficheiros maliciosos.

As diretrizes oficiais de administração recomendam uma segregação estrita entre permissões de leitura, escrita e execução:

# Definir todas as diretorias para 755 (rwxr-xr-x)
find /var/www/exemplo.pt/ -type d -exec chmod 755 {} \;

# Definir todos os ficheiros comuns para 644 (rw-r--r--)
find /var/www/exemplo.pt/ -type f -exec chmod 644 {} \;

# Restringir o ficheiro de configuração com credenciais da base de dados
chmod 400 /var/www/exemplo.pt/wp-config.php

Sob esta configuração, o processo do servidor web (www-data ou utilizador do pool PHP-FPM) não dispõe de permissões de escrita direta sobre os ficheiros PHP principais, com exceção da pasta wp-content/uploads/ destinada ao carregamento de ficheiros multimédia.

#Desativar a edição de ficheiros no painel do WordPress

Para evitar que invasores com credenciais administrativas alterem temas ou plugins através do editor integrado do WordPress, adicione a seguinte constante ao ficheiro wp-config.php:

define('DISALLOW_FILE_EDIT', true);

Esta instrução remove totalmente o submenu de edição de ficheiros do painel administrativo, eliminando uma via crítica de persistência de código malicioso.

#Como validar o hardening com curl

Após aplicar as configurações no código e nos servidores, valide o comportamento real através do terminal:

  1. Inspeção de cabeçalhos HTTP com cURL:
    curl -sI https://exemplo.pt | grep -iE "(strict-transport|x-frame|x-content|referrer|permissions|content-security)"
  2. Verificação de ausência da tag generator e das impressões digitais:
    curl -s https://exemplo.pt | grep -i "generator"
    curl -sI https://exemplo.pt | grep -iE "x-powered-by|x-pingback|api.w.org"
  3. Página de login com um único X-Frame-Options:
    curl -sI https://exemplo.pt/wp-login.php | grep -i x-frame
  4. Ficheiros bloqueados devem devolver 403:
    curl -sI https://exemplo.pt/xmlrpc.php | head -1
    curl -sI https://exemplo.pt/readme.html | head -1
    Repita a verificação dos cabeçalhos num URL que devolva 404. No Nginx sem always, é aí que os cabeçalhos desaparecem.
  5. Auditorias automatizadas: Submeta o domínio em serviços como o SecurityHeaders.com para aferir a pontuação global e confirmar a conformidade das diretivas declaradas.

#Conclusão

O endurecimento da infraestrutura do WordPress é uma disciplina contínua que combina boas práticas de desenvolvimento em PHP e rigor técnico na administração de sistemas. Ao eliminar os vestígios da versão do CMS e estabelecer uma defesa sólida com cabeçalhos de segurança e permissões de ficheiros restritivas, protege o seu negócio contra ataques oportunistas e ameaças automatizadas.

Para uma avaliação exaustiva dos vetores de risco da sua plataforma digital, consulte os nossos serviços de auditoria de segurança WordPress ou conte com o suporte técnico dos nossos especialistas WordPress.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Perguntas frequentes

Respostas práticas para aplicar o tema na execução real.

Esconder a versão do WordPress torna o website seguro#
Não de forma isolada. Ocultar a versão apenas dificulta o reconhecimento inicial por robôs de varredura. A segurança real assenta em atualizações contínuas, senhas fortes com autenticação de dois fatores (2FA) e cabeçalhos de segurança adequados.
A remoção de query strings quebra o carregamento de CSS e JS#
Se os parâmetros forem removidos de todos os recursos, os navegadores deixam de detetar ficheiros atualizados. Por isso, o filtro deste guia remove apenas a versão do núcleo e deixa os parâmetros de plugins e temas.
É seguro desativar totalmente o XML-RPC#
Na grande maioria dos sites, sim. Só o Jetpack e a aplicação móvel do WordPress com sites alojados pelo próprio dependem desta interface. O bloqueio deve ser feito no servidor web, porque o filtro xmlrpc_enabled deixa os pingbacks ativos.
Qual o impacto da Content Security Policy nos temas#
Uma política demasiado restritiva bloqueia fontes, vídeos externos ou scripts de construtores visuais. Teste primeiro com o cabeçalho Content-Security-Policy-Report-Only antes de impor a política em produção.
Ainda faz sentido enviar X-XSS-Protection#
Não. O MDN classifica o cabeçalho como obsoleto e a OWASP recomenda não o enviar ou defini-lo como 0. A proteção contra XSS vem de uma Content Security Policy.

Precisa de FAQ adaptado ao setor e mercado? Crio uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados

53 por cento de sites WordPress com CVE sem patch: auditoria GuardingWP

O primeiro relatório "State of WordPress Security 2026" da GuardingWP analisou 424 instalações WordPress confirmadas em mais de 40 setores. Resultado principal: mais de metade serve pelo menos um plugin com CVE conhecida e sem patch. O fundador da Patchstack, Oliver Sild, anunciou que o WordPress 7.0 vai desencadear "uma corrida absoluta dos hackers as chaves API". Polémica: 53 por cento não e descuido do utilizador, e o resultado estrutural da economia de plugins. A solução esta escrita no artigo 21 da NIS2 e no artigo 28 do DORA, mas exige um quadro de controlos e não a compra de mais uma firewall.

NIS2 e DORA em WordPress: o que um site tem de cumprir em 2026

A Diretiva NIS2 (2022/2555) deveria ter sido transposta para o direito nacional até 2024-10-17. O Regulamento DORA (2022/2554) aplica-se diretamente desde 2025-01-17. Para o operador de um site WordPress, isto significa obrigações concretas se o site disser respeito a uma entidade regulada. Explicamos sem pânico, com referências aos textos dos atos.