Cum rezolvi
Pașii de rezolvare pentru problemele raportate de WP Monitor. Fă un backup complet (fișiere și bază de date) înainte de orice modificare. Regulile de server sunt date pentru Apache (în fișierul .htaccess din rădăcina site-ului; LiteSpeed citește același fișier) și pentru nginx (în configurația site-ului; reîncarcă nginx după aceea).
Actualizează WordPress
Un WordPress neactualizat este cea mai frecventă cale prin care site-urile sunt sparte. Corecturile de securitate apar doar pentru versiunea curentă, așa că o versiune cu o breșă cunoscută trebuie actualizată chiar în aceeași zi.
- În zona de administrare: Panou de control > Actualizări, apoi „Actualizează la versiunea …”.
- Verifică apoi site-ul: pagina principală, câteva pagini, formularul de contact și, dacă ai magazin, finalizarea comenzii.
- Lasă pornite actualizările automate minore (implicit). Ele instalează singure versiunile de securitate.
Cu acces shell, WP-CLI face același lucru din folderul site-ului:
wp core update
wp core update-db
Actualizează pluginurile și temele
Pluginurile și temele sunt cealaltă cale frecventă de atac: majoritatea alertelor de securitate WordPress sunt despre ele.
- Mergi la Module > Module instalate și la Aspect > Teme și actualizează tot ce are actualizare.
- Șterge pluginurile și temele pe care nu le folosești. Un plugin dezactivat rămâne pe server, iar fișierele lui pot fi atacate în continuare.
- Pluginurile și temele premium se actualizează doar cu licență validă. Dacă a expirat, reînnoiește-o sau înlocuiește pluginul.
- Ia în calcul actualizările automate pentru pluginurile în care ai încredere („Activează actualizările automate” în lista de module).
wp plugin update --all
wp theme update --all
Activează cache-ul de pagină
Fără cache de pagină, WordPress construiește fiecare pagină din PHP și baza de date la fiecare vizită. Un cache de pagină salvează pagina gata construită și o livrează direct – de obicei de câteva ori mai repede și mult mai rezistent la vârfuri de trafic.
- WP Rocket (plătit): funcționează pe orice server, cu setări implicite bune.
- W3 Total Cache (gratuit): multe opțiuni; alege „Disk: Enhanced” la cache-ul de pagină.
- WP-Optimize (gratuit): cache de pagină plus curățarea bazei de date, ușor de configurat.
- Dacă găzduirea ta folosește serverul web LiteSpeed, folosește în schimb LiteSpeed Cache: se bazează pe cache-ul integrat al serverului. Pe Apache sau nginx este limitat, acolo alege unul dintre pluginurile de mai sus.
Folosește un singur plugin de cache de pagină. Unele firme de găzduire (SiteGround, Kinsta, WP Engine și altele) păstrează deja paginile în cache pe server; atunci nu e nevoie de un plugin pentru asta.
Înapoi susActivează compresia
Compresia (Gzip sau Brotli) face paginile, CSS-ul și JavaScript-ul cu 60–80% mai mici pe drumul spre vizitator. Pluginurile de cache de mai sus o pot porni, sau se setează în configurația serverului:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
AddOutputFilterByType DEFLATE application/javascript text/javascript application/json
AddOutputFilterByType DEFLATE application/xml application/rss+xml image/svg+xml
</IfModule>
# in the http { } or server { } block
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain text/css text/xml application/javascript text/javascript
application/json application/xml application/rss+xml image/svg+xml;
Cache în browser pentru CSS și JavaScript
Când CSS-ul, JavaScript-ul, imaginile și fonturile sunt trimise cu o durată lungă de cache, vizitatorii care revin și fiecare pagină următoare le iau din browser în loc să le descarce din nou. WordPress adaugă acestor fișiere o versiune (?ver=...) care se schimbă când se schimbă fișierul, deci o durată lungă e sigură.
Majoritatea pluginurilor de cache au o opțiune „browser caching”. Sau setează pe server:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
# in the server { } block
location ~* \.(?:css|js|webp|jpe?g|png|gif|svg|ico|woff2?)$ {
expires 30d;
access_log off;
try_files $uri =404;
}
nginx: un bloc location cu propriile linii add_header nu mai moștenește liniile add_header din blocul server, așa că repetă acolo header-ele de securitate dacă adaugi header-e în el.
Grăbește primul răspuns
Este timpul până când pagina începe să sosească, măsurat de pe serverul nostru. Peste 1,5 secunde înseamnă de obicei că pagina e construită de la zero la fiecare vizită.
- Activează un cache de pagină (vezi mai sus). De obicei asta rezolvă singur problema.
- Dacă ai deja un cache activ, caută un plugin lent: un plugin ca Query Monitor arată care pluginuri încetinesc paginile.
- Folosește o versiune PHP actuală (8.2 sau mai nouă), cu OPcache activat.
- Dacă nimic din acestea nu ajută, planul de găzduire e probabil prea mic pentru site.
Repară certificatul SSL
Când certificatul nu e valid pentru adresă, vizitatorii văd un avertisment al browserului pe toată pagina („Conexiunea nu este privată”) în loc de site, motoarele de căutare îl scot din rezultate, iar formularele și autentificările nu sunt sigure. Ce faci depinde de ce am găsit:
- Certificat pentru alt nume: serverul răspunde cu un certificat pentru alte nume, de obicei cel implicit al firmei de găzduire. Site-ul nu are un certificat propriu pe acel server: adaugă unul pentru example.com și www.example.com.
- Expirat: reînnoiește-l. Certificatele gratuite Let’s Encrypt sunt valabile 90 de zile și trebuie să se reînnoiască automat; dacă unul a expirat, reînnoirea automată nu funcționează.
- Auto-semnat sau neîncrezut: browserele au încredere doar în certificatele emise de o autoritate publică. Înlocuiește-l cu un certificat Let’s Encrypt (gratuit) sau comercial și instalează lanțul complet (fișierul „CA bundle” sau „fullchain”), nu doar certificatul.
- Fără HTTPS deloc: instalează un certificat, apoi redirecționează http:// către https://.
Pe găzduire partajată folosește panoul de control: AutoSSL în cPanel, „SSL It!” sau Let’s Encrypt în Plesk, sau cere firmei de găzduire. Pe serverul propriu, certbot obține și reînnoiește certificate Let’s Encrypt:
# free Let's Encrypt certificate, renewed automatically (run as root)
certbot --nginx -d example.com -d www.example.com # nginx
certbot --apache -d example.com -d www.example.com # Apache
După ce HTTPS funcționează, redirecționează http:// către https://:
# .htaccess, above the # BEGIN WordPress block
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
- Verificare: deschide site-ul într-o fereastră privată, cu și fără www. Trebuie să apară lacătul și niciun avertisment.
Dezactivează XML-RPC
xmlrpc.php este o interfață veche de acces la distanță. Atacatorii o folosesc pentru a încerca sute de parole într-o singură cerere. Majoritatea site-urilor nu mai au nevoie de ea; pluginul Jetpack și unele aplicații de publicare au, așa că sari peste acest pas dacă le folosești.
<Files "xmlrpc.php">
Require all denied
</Files>
location = /xmlrpc.php {
deny all;
}
Și pluginurile de securitate (Wordfence, Solid Security și altele) au o opțiune pentru dezactivarea XML-RPC.
Înapoi susElimină tag-ul generator WordPress
Implicit, WordPress își scrie versiunea exactă în fiecare pagină: <meta name="generator" content="WordPress 7.1.2">. Asta le spune atacatorilor ce breșe cunoscute să încerce.
Elimină-l cu acest cod sau cu opțiunea „ascunde versiunea WordPress” dintr-un plugin de securitate (Wordfence, Solid Security și altele au una):
// functions.php of a child theme, or a small custom plugin
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
- Pune codul în
functions.phpal unei teme copil sau într-un mic plugin propriu – nu în tema principală: o actualizare a temei l-ar șterge. - Golește apoi cache-ul de pagină, altfel vizitatorii primesc în continuare paginile vechi, cu tag-ul.
- Verificare: deschide pagina principală, vezi codul sursă (Ctrl+U) și caută „generator”. Nu trebuie să mai apară „WordPress”.
Blochează accesul la readme.html
Orice instalare WordPress are un fișier readme.html în rădăcina site-ului, care afișează versiunea WordPress oricui îl deschide.
Ștergerea nu ține: WordPress îl pune la loc la fiecare actualizare. Blochează în schimb accesul la el pe server; regula rămâne și după actualizări.
Apache și LiteSpeed: adaugă asta în fișierul .htaccess din rădăcina site-ului (folderul cu wp-config.php), deasupra sau sub blocul # BEGIN WordPress, nu în interiorul lui (WordPress rescrie acel bloc):
<Files "readme.html">
Require all denied
</Files>
nginx: adaugă asta în blocul server { } al site-ului, apoi verifică și reîncarcă configurația (nginx -t, apoi systemctl reload nginx). O potrivire exactă location = are prioritate față de celelalte reguli:
location = /readme.html {
deny all;
}
- Verificare: deschide
https://site-ul-tau/readme.html. Trebuie să primești „403 Forbidden” în loc de pagina readme a WordPress. - Pe găzduire partajată, fără acces la configurația serverului, folosește regula din
.htaccess; managerul de fișiere din cPanel sau Plesk o poate edita (activează „afișează fișierele ascunse”).
Nu mai lista numele de utilizator
Implicit, /wp-json/wp/v2/users listează numele de utilizator ale tuturor celor care au publicat conținut – jumătate din datele de autentificare, oferite atacatorilor. Acest cod ascunde lista de vizitatorii nelogați, iar editorul de blocuri funcționează în continuare:
// functions.php of a child theme, or a small custom plugin
add_filter('rest_endpoints', function ($endpoints) {
if (!is_user_logged_in()) {
unset($endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});
Majoritatea pluginurilor de securitate au o opțiune similară („disable user enumeration”).
Înapoi susDezactivează listarea directoarelor
Cu listarea directoarelor pornită, oricine poate vedea lista fișierelor dintr-un folder precum /wp-content/uploads/, inclusiv fișiere private și backup-uri.
Options -Indexes
# nginx lists directories only where autoindex is switched on: remove it, or set
autoindex off;
Elimină jurnalul de debug public
wp-content/debug.log poate conține căi de pe server, erori de pluginuri și uneori detalii despre baza de date, iar oricine îl poate descărca. Șterge fișierul acum, apoi oprește WordPress să-l mai scrie acolo:
// wp-config.php
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
// or, to keep logging, write the log outside the website folder:
// define('WP_DEBUG_LOG', '/home/USER/logs/wp-debug.log');
Și blochează numele fișierului, în caz că jurnalizarea e pornită din nou mai târziu:
<Files "debug.log">
Require all denied
</Files>
location ~* /debug\.log$ {
deny all;
}
Ascunde versiunea Apache, nginx și PHP
Header-e de răspuns precum Server: nginx/1.30.5 sau X-Powered-By: PHP/8.2.12 le spun atacatorilor exact pentru ce versiuni să caute breșe cunoscute. Doar numele serverului (nginx, Apache) nu e o problemă; numărul versiunii trebuie ascuns. Asta nu înlocuiește actualizările, doar oprește afișarea versiunii.
Apache: în configurația principală a serverului (nu se poate în .htaccess). Pe găzduire partajată întreabă firma de găzduire; majoritatea fac deja asta.
# main Apache configuration (not .htaccess), e.g. /etc/apache2/conf-enabled/security.conf
# or /etc/httpd/conf/httpd.conf, then reload Apache
ServerTokens Prod
ServerSignature Off
nginx: în blocul http { }. fastcgi_hide_header elimină și header-ul cu versiunea PHP:
# in the http { } block of nginx.conf, then: nginx -t && systemctl reload nginx
server_tokens off;
# hides the PHP version header coming from PHP-FPM
fastcgi_hide_header X-Powered-By;
PHP: oprește PHP să mai trimită X-Powered-By. expose_php se poate seta doar în php.ini sau în pool-ul PHP-FPM, nu în .user.ini:
; php.ini (or the PHP-FPM pool: php_admin_flag[expose_php] = off), then restart PHP-FPM
expose_php = Off
Fără acces la php.ini (găzduire partajată pe Apache sau LiteSpeed), elimină header-ul din .htaccess:
# .htaccess: removes the PHP version header when you cannot change php.ini
<IfModule mod_headers.c>
Header always unset X-Powered-By
Header unset X-Powered-By
</IfModule>
- Verificare:
curl -I https://site-ul-tau/trebuie să arateServer: nginxsauServer: Apachefără număr și nicio linieX-Powered-By: PHP/....
Adaugă header-e de securitate
Trei header-e de răspuns blochează atacuri frecvente din browser:
Strict-Transport-Security(HSTS): browserele folosesc mereu HTTPS pentru site-ul tău, chiar dacă cineva scrie sau trimite un link cu http://. Adaugă-l doar după ce HTTPS funcționează pe tot site-ul.X-Frame-Options: alte site-uri nu îți pot afișa paginile într-un frame (clickjacking).X-Content-Type-Options: browserele nu ghicesc tipul fișierelor, ceea ce blochează unele trucuri de injectare de scripturi.
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
</IfModule>
# in the server { } block that serves HTTPS
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;