.HTACCESS
HTTPS forceren (alle verkeer)
Stuur al het HTTP-verkeer naar HTTPS via een 301-redirect. Plaats bovenaan in .htaccess, boven de WordPress-blok.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
www → non-www redirect
.HTACCESS
Forceer één canonieke domeinvariant. Voorkomt duplicate content en cookie-conflicten. Vervang voorbeeld.nl.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.voorbeeld\.nl$ [NC]
RewriteRule ^(.*)$ https://voorbeeld.nl/$1 [L,R=301]
.HTACCESS
non-www → www redirect
De andere kant op. Sommige hosters / mailservers eisen www-variant.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^voorbeeld\.nl$ [NC]
RewriteRule ^(.*)$ https://www.voorbeeld.nl/$1 [L,R=301]
.HTACCESS
Specifieke pagina 301-redirect
Verplaats één URL naar een nieuwe locatie. Handig na content-restructuring of CMS-migraties.
Redirect 301 /oude-pagina/ https://voorbeeld.nl/nieuwe-pagina/
Redirect 301 /blog/2019/post-naam.html https://voorbeeld.nl/blog/post-naam/
.HTACCESS
Gzip-compressie aanzetten
Comprimeer HTML, CSS, JS en JSON. Bespaart 60–75% bandbreedte. Werkt alleen als mod_deflate beschikbaar is.
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/x-javascript
AddOutputFilterByType DEFLATE application/json application/xml application/rss+xml
AddOutputFilterByType DEFLATE image/svg+xml application/wasm
AddOutputFilterByType DEFLATE font/ttf font/otf application/x-font-ttf
</IfModule>
.HTACCESS
Browser-caching (Expires + Cache-Control)
Statische assets één jaar cachen, HTML niet. Verlaagt repeat-visit laadtijd flink.
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
.HTACCESS
Hotlink-bescherming voor afbeeldingen
Blokkeer dat andere sites jouw afbeeldingen rechtstreeks inladen en jouw bandbreedte opslokken. Vervang voorbeeld.nl.
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?voorbeeld\.nl [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?google\. [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg)$ - [F,NC]
.HTACCESS
WordPress permalinks (standaard rewrite-blok)
De standaard WP-rewrite-regel voor mooie URL's. WordPress schrijft 'm zelf, maar als je permalinks "kapot" zijn (bv. 404 op alles behalve homepage) ontbreekt deze blok of is 'ie overschreven.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
.HTACCESS
wp-admin / wp-login afschermen op IP
Beperk toegang tot WordPress-admin tot specifieke IP's. Beste bescherming tegen brute-force, geen plugin nodig. Plaats in .htaccess in WordPress-root.
<Files wp-login.php>
Require ip 81.123.45.67
Require ip 145.92.10.0/24
</Files>
<Files xmlrpc.php>
Require all denied
</Files>
.HTACCESS
PHP-uitvoering blokkeren in /uploads/
Top-3 hack-prevention. Plaats in een nieuw .htaccess direct binnen /wp-content/uploads/ (niet in site-root!). Voorkomt dat geüploade .php-malware draait — de meest gebruikte WP-hack-route.
<Files *.php>
Require all denied
</Files>
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|php8|phar)$">
Require all denied
</FilesMatch>
.HTACCESS
Author enumeration blokkeren
WordPress lekt usernames via ?author=1 — de redirect onthult de admin-slug. Met deze regel krijgt iedereen die het probeert een 403. Plaats in .htaccess in site-root.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)author=\d+ [NC]
RewriteRule .* - [F]
</IfModule>
WORDPRESS
WP_DEBUG aanzetten + log-bestand
Plaats in wp-config.php bóven de regel "That's all, stop editing". Errors gaan dan naar wp-content/debug.log in plaats van zichtbaar op de site.
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
define( 'SCRIPT_DEBUG', false );
WORDPRESS
Memory-limit verhogen (wp-config)
Bij "Allowed memory size exhausted". Werkt alleen als de host PHP-memory laat ophogen door PHP-code; lukt het niet, dan via php.ini of .htaccess.
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
WORDPRESS
Plugin/theme file-editor uitschakelen
Als een aanvaller toegang krijgt tot wp-admin kan ie via de file-editor PHP injecteren. Met deze regel niet meer.
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );
WORDPRESS
SSL forceren voor wp-admin
Voorkomt dat admin-cookies over HTTP gaan. Combineer met de .htaccess HTTPS-redirect voor volledige dekking.
define( 'FORCE_SSL_ADMIN', true );
// Achter Cloudflare/loadbalancer:
if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
$_SERVER['HTTPS'] = 'on';
}
WORDPRESS
WP-Cron uitschakelen + server-cron
Op drukke sites wordt WP-Cron bij elke pageview gecheckt — verspilling. Beter: server-cron elke 5 min.
// In wp-config.php:
define( 'DISABLE_WP_CRON', true );
// In server crontab (crontab -e):
*/5 * * * * curl -s https://voorbeeld.nl/wp-cron.php?doing_wp_cron > /dev/null 2>&1
WORDPRESS
Site-URL forceren (na hack of migratie)
Als de site-URL in de database verkeerd staat (na hack of bij migratie), overrule je 'm vanuit wp-config.php. Hardcoded, geen DB-edit nodig.
define( 'WP_HOME', 'https://voorbeeld.nl' );
define( 'WP_SITEURL', 'https://voorbeeld.nl' );
WORDPRESS
Admin script-concatenation uitschakelen
WordPress voegt JS-bestanden in admin standaard samen tot één grote URL. Dat breekt soms (admin-balk laadt niet, blokken-editor crasht, multisite met grote netwerken). Zet uit en de admin laadt JS apart.
define( 'CONCATENATE_SCRIPTS', false );
WORDPRESS
FS_METHOD = direct (FTP-prompt fix)
Bij plugin- of theme-updates vraagt WordPress soms ineens om FTP-gegevens. Komt door verkeerde file-ownership op de server. Forceer de direct file-write methode.
define( 'FS_METHOD', 'direct' );
WORDPRESS
Externe HTTP-calls blokkeren (whitelist)
Blokkeer alle uitgaande HTTP-calls vanuit WordPress (plugins die naar hun eigen server "bellen", telemetry, license-checks). Whitelist alleen wat je echt nodig hebt — bv. wordpress.org voor updates.
define( 'WP_HTTP_BLOCK_EXTERNAL', true );
define( 'WP_ACCESSIBLE_HOSTS', 'api.wordpress.org,*.wordpress.org,downloads.wordpress.org' );
WORDPRESS
WP_ALLOW_REPAIR — ingebouwde DB-repair
WordPress heeft een ingebouwde database-repair tool, standaard uit. Zet aan, gebruik één keer, en zet daarna direct uit — de repair-URL vereist namelijk geen login.
define( 'WP_ALLOW_REPAIR', true );
// Open daarna in je browser:
// https://voorbeeld.nl/wp-admin/maint/repair.php
//
// Direct na gebruik weer uit, deze URL is publiek toegankelijk.
WORDPRESS
Auto-updates volledig uitschakelen
Voor sites met een staging-flow waar je controle wilt over wat live gaat. Schakelt álle automatische updates uit (core, minor, security, plugins).
define( 'AUTOMATIC_UPDATER_DISABLED', true );
define( 'WP_AUTO_UPDATE_CORE', false );
WORDPRESS
Autosave + revisions limiteren
Voorkom dat wp_posts opzwelt met duizenden oude revisies. Per post max 5 versies bewaren, autosave naar 2 minuten.
define( 'WP_POST_REVISIONS', 5 );
define( 'AUTOSAVE_INTERVAL', 120 );
define( 'EMPTY_TRASH_DAYS', 14 );
WORDPRESS
Emergency password reset (functions.php)
Buitengesloten uit wp-admin? Plak dit éénmalig in functions.php van je actieve theme, laad de site, log in, en verwijder het direct daarna.
// LOAD SITE EENS, DAN DIT BLOK METEEN VERWIJDEREN
add_action( 'init', function() {
$user = get_user_by( 'login', 'admin' );
if ( $user ) {
wp_set_password( 'TijdelijkW8w00rd!2026', $user->ID );
}
});
WORDPRESS
XML-RPC volledig uitschakelen
XML-RPC wordt zelden nog legitiem gebruikt, maar is een populaire aanvalsvector (brute-force via system.multicall, pingback-amplification). Plak in functions.php of in een mu-plugin.
add_filter( 'xmlrpc_enabled', '__return_false' );
add_filter( 'wp_headers', function( $headers ) {
unset( $headers['X-Pingback'] );
return $headers;
});
remove_action( 'wp_head', 'rsd_link' );
WORDPRESS
Gutenberg uit, Classic Editor terug
Schakel de blokken-editor volledig uit en val terug op de oude TinyMCE-editor — zonder de Classic Editor plugin. Plak in functions.php.
add_filter( 'use_block_editor_for_post', '__return_false', 10 );
add_filter( 'use_block_editor_for_post_type', '__return_false', 10 );
// Verwijder ook de "Probeer Gutenberg"-banner in admin
remove_action( 'try_gutenberg_panel', 'wp_try_gutenberg_panel' );
WORDPRESS
Upload-limiet ophogen
Bij "The uploaded file exceeds the upload_max_filesize". Drie methodes — gebruik degene die werkt op jouw hosting.
# Optie 1: php.ini (eigen VPS / cPanel MultiPHP)
upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 300
# Optie 2: .htaccess (shared hosting met PHP als module)
php_value upload_max_filesize 128M
php_value post_max_size 128M
# Optie 3: .user.ini (PHP als CGI/FPM)
upload_max_filesize = 128M
post_max_size = 128M
WP-CLI
wp search-replace (URL's veilig vervangen)
Bij sitemigratie of HTTP→HTTPS-switch moet je oude URL's overal vervangen, óók in geserialiseerde array's (widget-options, theme-mods). Een gewone UPDATE-query sloopt die. WP-CLI doet het veilig.
# Eerst dry-run om te zien wat verandert (verandert niets!)
wp search-replace 'http://oude-site.nl' 'https://voorbeeld.nl' --all-tables --dry-run
# Daarna het echte werk
wp search-replace 'http://oude-site.nl' 'https://voorbeeld.nl' --all-tables
# Specifieke tabel + GUID-veld overslaan (best practice voor wp_posts)
wp search-replace 'oude-string' 'nieuwe-string' wp_posts --skip-columns=guid
# Tip: maak altijd eerst een DB-backup
wp db export backup-voor-replace-$(date +%Y%m%d).sql
DNS
SPF voor Microsoft 365
Standaard SPF-record als je mail uitsluitend via Microsoft 365 verstuurt. Type: TXT, naam: @ (root van het domein).
v=spf1 include:spf.protection.outlook.com -all
DNS
SPF voor Google Workspace
SPF-record voor mail uitsluitend via Google Workspace. Voeg extra include: toe als je ook bv. Mailchimp of een CRM gebruikt.
v=spf1 include:_spf.google.com -all
DNS
DMARC start (monitoring)
Eerste DMARC-record: alleen rapporteren, niet blokkeren. Plaats op _dmarc.voorbeeld.nl (TXT). Vervang het rapportage-mailadres.
v=DMARC1; p=none; rua=mailto:dmarc@voorbeeld.nl; ruf=mailto:dmarc@voorbeeld.nl; fo=1; adkim=r; aspf=r; pct=100
DNS
DMARC strikt (reject)
Stap 3 van DMARC-roll-out: spoofers worden geweigerd. Pas dit pas toe als je weken op p=quarantine hebt gedraaid en alle eigen mailstromen kloppen.
v=DMARC1; p=reject; rua=mailto:dmarc@voorbeeld.nl; adkim=s; aspf=s; pct=100
DNS
CAA voor Let's Encrypt
CAA bepaalt welke CA's een certificaat voor jouw domein mogen uitgeven. Voorkomt dat een aanvaller bij een andere CA een vals cert aanvraagt.
voorbeeld.nl. IN CAA 0 issue "letsencrypt.org"
voorbeeld.nl. IN CAA 0 iodef "mailto:security@voorbeeld.nl"
DNS
Autodiscover voor Microsoft 365
Zorgt dat Outlook automatisch de juiste server-instellingen vindt bij toevoegen van een mailbox. Type CNAME, naam autodiscover.
autodiscover.voorbeeld.nl. CNAME autodiscover.outlook.com.
NGINX
HTTPS-redirect (Nginx server block)
Aparte server-block op poort 80 die al het verkeer 301-redirect naar HTTPS. Plaats vóór je https-server block.
server {
listen 80;
listen [::]:80;
server_name voorbeeld.nl www.voorbeeld.nl;
return 301 https://voorbeeld.nl$request_uri;
}
NGINX
WordPress permalink rewrite (Nginx)
Het Nginx-equivalent van de WordPress .htaccess-blok. Plaats binnen je server { } block.
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
NGINX
Dotfiles + gevoelige bestanden blokkeren
Verhinder publieke toegang tot .git/, .env, backup-files en log-files. Een van de meest onderschatte security-fixes.
location ~ /\.(?!well-known) {
deny all;
}
location ~* \.(env|log|sql|sqlite|conf|ini|bak|backup|old|swp)$ {
deny all;
}
location ~ /(wp-config\.php|configuration\.php|sites/default/settings\.php)$ {
deny all;
}
NGINX
Gzip-compressie (Nginx)
Plaats in nginx.conf of in een server-block. gzip_comp_level 6 is de sweet-spot tussen CPU en compressie-ratio.
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types
text/plain text/css text/xml text/javascript
application/javascript application/x-javascript
application/json application/xml application/rss+xml
image/svg+xml font/ttf font/otf application/wasm;
NGINX
Static assets cachen (Nginx)
Geef browsers expliciet een 30-dagen cache voor statische bestanden. Verlaagt repeat-visit laadtijd zonder server-roundtrip.
location ~* \.(jpg|jpeg|png|gif|webp|svg|ico|woff2|css|js)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
PHP.INI
Upload + post-size verhogen
Standaard zit PHP op 2–8 MB. Voor mediabibliotheken en grote backups vaak te krap.
upload_max_filesize = 128M
post_max_size = 128M
max_input_time = 300
max_execution_time = 300
max_input_vars = 3000
PHP.INI
Memory + execution-time
Voor zware imports, backups, of complexe page-builders. 256M is genoeg voor 95% van WordPress-sites.
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
PHP.INI
Display errors — productie vs. dev
Productie: errors logboeken, niet tonen aan bezoekers. Development: alles laten zien. Mix de twee niet.
# PRODUCTIE
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php-errors.log
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED
# DEVELOPMENT
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
PHP.INI
Timezone op Europe/Amsterdam
Voorkomt dat date() en cron-jobs in UTC of een andere zone draaien. Zet ook in WordPress > Algemeen.
date.timezone = "Europe/Amsterdam"
MAIL-DEBUG
MX-records opzoeken (dig)
De waarheid over waar mail voor een domein heen gaat. +short geeft compacte output zonder ruis.
dig MX voorbeeld.nl +short
# Of via Google's DNS, om je eigen ISP-cache te omzeilen:
dig @8.8.8.8 MX voorbeeld.nl +short
MAIL-DEBUG
SPF-record uitlezen
SPF zit in een TXT-record. Pluk de SPF-regel eruit met grep.
dig TXT voorbeeld.nl +short | grep spf1
MAIL-DEBUG
DKIM-key uitlezen (met selector)
DKIM-records staan op {selector}._domainkey.{domein}. Selector verschilt per provider: M365 gebruikt selector1/selector2, Google google.
# Microsoft 365
dig TXT selector1._domainkey.voorbeeld.nl +short
dig TXT selector2._domainkey.voorbeeld.nl +short
# Google Workspace
dig TXT google._domainkey.voorbeeld.nl +short
MAIL-DEBUG
DMARC-record uitlezen
DMARC zit altijd op _dmarc.{domein}. Toont policy (p=none/quarantine/reject) en rapportage-mailadres.
dig TXT _dmarc.voorbeeld.nl +short
MAIL-DEBUG
SMTP-server testen met swaks
swaks is de beste SMTP-debug tool die er is. Test met TLS, verzendt echte mail, toont elke server-reactie.
# Installeer eerst (Debian/Ubuntu): apt install swaks
swaks --to test@gmail.com \
--from info@voorbeeld.nl \
--server mail.voorbeeld.nl \
--port 587 \
--auth LOGIN \
--auth-user info@voorbeeld.nl \
--tls \
--header "Subject: SMTP test" \
--body "Test van swaks via 587/TLS"
LINUX
20 grootste bestanden vinden
Schijf vol? Snel achterhalen welke bestanden de boosdoeners zijn. Sorteert op grootte aflopend.
find /var/www -type f -exec du -h {} + 2>/dev/null | sort -rh | head -20
LINUX
Disk-usage per submap (gesorteerd)
Welke map vreet je quota op? du -h --max-depth=1 geeft per directory de totale grootte.
du -h --max-depth=1 /var/www 2>/dev/null | sort -rh | head -20
LINUX
Live error-log volgen
tail -f toont nieuwe regels zodra ze verschijnen. Onmisbaar bij realtime debuggen tijdens een testactie.
# Apache
tail -f /var/log/apache2/error.log
# Nginx
tail -f /var/log/nginx/error.log
# PHP-FPM
tail -f /var/log/php8.2-fpm.log
# Filter alleen op fatal/error niveau
tail -f /var/log/apache2/error.log | grep -iE "fatal|error|warning"
LINUX
Bestanden gewijzigd in laatste 24u (na hack)
Dé eerste actie bij vermoeden van een hack: zoek alles wat gisteren is aangepast. Vervang -mtime -1 met -mtime -7 voor laatste 7 dagen.
# Alle gewijzigde files in laatste 24 uur
find /var/www -type f -mtime -1 -ls
# Alleen PHP-bestanden, laatste 7 dagen
find /var/www -type f -name "*.php" -mtime -7 -ls
# Sorteer op modify-time, nieuwste eerst
find /var/www -type f -mtime -7 -printf "%T+ %p\n" | sort -r | head -50
LINUX
Top 10 processen op CPU/memory
Snel zien wat er load op de server zet. top/htop is interactief, deze one-liner geeft een snapshot.
# Top CPU
ps aux --sort=-%cpu | head -11
# Top memory
ps aux --sort=-%mem | head -11
# Hele snapshot, alleen PHP/MySQL
ps aux | grep -E "php|mysql" | grep -v grep
MYSQL
Database backup met mysqldump
Standaard manier om een SQL-export te maken. Met --single-transaction blokkeer je de site niet tijdens backup van InnoDB-tabellen.
mysqldump -u USER -p \
--single-transaction \
--quick \
--default-character-set=utf8mb4 \
DATABASE_NAAM > backup-$(date +%Y%m%d).sql
# Direct gzippen (scheelt 80%):
mysqldump -u USER -p DATABASE_NAAM | gzip > backup-$(date +%Y%m%d).sql.gz
MYSQL
Database restore (import)
Plaats een SQL-bestand terug. Voor gzippede backups: gunzip on-the-fly via pipe.
# Plain SQL
mysql -u USER -p DATABASE_NAAM < backup.sql
# Gzippede backup
gunzip < backup.sql.gz | mysql -u USER -p DATABASE_NAAM
MYSQL
Grootste tabellen in een database
Laadt je WP-site traag? wp_options die honderden MB groot is, is een veel voorkomende boosdoener.
SELECT
table_name AS 'Tabel',
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Grootte (MB)',
table_rows AS 'Rijen'
FROM information_schema.TABLES
WHERE table_schema = 'DATABASE_NAAM'
ORDER BY (data_length + index_length) DESC
LIMIT 10;
MYSQL
WordPress site-URL via SQL veranderen
Bij migratie of na een hack waarbij siteurl is gekaapt. Let op: dit verandert alleen wp_options — voor URL's in posts/meta gebruik je wp search-replace via WP-CLI.
UPDATE wp_options
SET option_value = 'https://voorbeeld.nl'
WHERE option_name IN ('siteurl', 'home');
-- Daarna voor de rest van de DB (in shell):
-- wp search-replace 'https://oude-url.nl' 'https://voorbeeld.nl' --all-tables
MYSQL
Admin-wachtwoord resetten via SQL
Buitengesloten en mail-reset werkt niet (DNS kapot, mail-server down, mailbox vol)? Reset met een MD5-hash via SQL. WordPress upgrade het hash automatisch naar phpass bij eerstvolgende login.
UPDATE wp_users
SET user_pass = MD5( 'NieuwTijdelijkW8w00rd!' )
WHERE user_login = 'admin';
-- Verander direct na inloggen je wachtwoord via de admin-UI
-- (zo krijg je een veilige phpass-hash + nieuwe sessie-keys).
MYSQL
Alle plugins uitzetten via SQL
Witte scherm of fatal error na een plugin-update, en geen toegang tot wp-admin? Deze query zet álle plugins in één keer uit. Lege geserialiseerde array = geen actieve plugins.
UPDATE wp_options
SET option_value = 'a:0:{}'
WHERE option_name = 'active_plugins';
-- Daarna log je in en activeer je ze één voor één
-- om te vinden welke plugin het probleem veroorzaakte.