WordPress vraagt om FTP-gegevens bij plugin-installatie: zo los je het op

Je klikt op Plugin installeren en in plaats van de installatie krijg je een formulier met Verbindingsgegevens, dat om een FTP-host, gebruikersnaam en wachtwoord vraagt. Dit artikel legt de eigenaarscontrole uit die het formulier triggert, loopt de juiste fix langs (chown naar de PHP-gebruiker), behandelt de wp-config.php-shortcuts en wanneer elke shortcut veilig is, en lijst de typische Nginx- en Docker-scenario's op waarin je dit ziet.

Je klikt op Plugins > Nieuwe plugin, kiest een plugin, drukt op Nu installeren, en in plaats van de voortgangsbalk krijg je een formulier met de titel Verbindingsgegevens, dat vraagt om een Hostnaam, FTP-gebruikersnaam, FTP-wachtwoord en een keuze tussen FTP en FTPS. Hetzelfde scherm verschijnt als je op Nu bijwerken klikt voor een plugin of thema. Dat formulier invullen werkt vaak niet eens, want op de meeste servers draait helemaal geen FTP. De oplossing is bijna nooit om gegevens in te vullen. De oplossing is om de eigenaarscontrole te laten slagen.

Wat het FTP-formulier eigenlijk betekent

WordPress vraagt niet om FTP-gegevens omdat het zo dol is op FTP. Het vraagt erom omdat een runtime-check in get_filesystem_method() heeft besloten dat het PHP-proces dat WordPress draait niet de eigenaar is van de WordPress-bestanden, en dus niet veilig direct kan schrijven.

De functie staat in wp-admin/includes/file.php en draait elke keer dat WordPress een plugin, thema, taalpakket of core-update wil installeren. Vereenvoudigd doet hij dit:

// Maak een tijdelijk bestand in wp-content
$temp_file_name = $context . 'temp-write-test-' . uniqid( '', true );
$temp_handle    = @fopen( $temp_file_name, 'w' );

if ( $temp_handle ) {
    // Vergelijk de UID die wp-admin/includes/file.php bezit
    // met de UID die zojuist het tijdelijke bestand schreef
    $wp_file_owner   = @fileowner( __FILE__ );
    $temp_file_owner = @fileowner( $temp_file_name );

    if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) {
        $method = 'direct';
    }
}

De logica: als de gebruiker die de WordPress core-bestanden bezit dezelfde is als de gebruiker die zojuist een tijdelijk bestand in wp-content/ heeft geschreven, dan draait PHP zelf als de eigenaar van die bestanden. PHP kan dan direct plugin-bestanden aanmaken, wijzigen en verwijderen zonder hulp van buitenaf. WordPress zet direct als methode en de installatie verloopt geruisloos.

Als de twee UIDs niet overeenkomen, valt de check door. WordPress kijkt vervolgens of er een SSH2-verbinding is, daarna de FTP PHP-extensie, dan socket-gebaseerd FTP, en als geen daarvan vooraf is geconfigureerd komt het uit op het laatste redmiddel: een formulier voor FTP-gegevens. Dat is wat je voor je ziet.

De volgorde staat beschreven onder WP_Filesystem in de Advanced Administration handbook: direct, dan SSH2, dan FTP-extensie, dan FTP-sockets, dan vragen.

De vraag is dus nooit "welke FTP-gegevens moet ik hier intypen". De vraag is "waarom komen die UIDs niet overeen", en het antwoord is bijna altijd een van dezelfde drie setups.

De twee diagnostische vragen

Voor je iets aanpast, beantwoord je deze twee vragen op de server zelf. Ze vertellen je welke fix je nodig hebt.

Vraag 1: wie bezit de WordPress-bestanden?

# Vervang /var/www/html door het pad naar je WordPress-installatie
stat -c '%U %u %n' /var/www/html/wp-admin/includes/file.php

De output ziet er ongeveer zo uit: deployer 1001 /var/www/html/wp-admin/includes/file.php. De eerste kolom is de gebruikersnaam, de tweede is de numerieke UID. Schrijf ze allebei op.

Vraag 2: wie draait PHP?

Het antwoord hangt af van hoe PHP is opgehangen. De meest betrouwbare check is om het PHP zelf te vragen:

# Draai een one-liner als de webserver-gebruiker
sudo -u www-data php -r 'echo posix_getpwuid(posix_geteuid())["name"], " ", posix_geteuid(), PHP_EOL;'

Of, als PHP-FPM draait, kijk naar de pool-config:

# Vind het PHP-FPM pool-bestand en grep de user/group regels
grep -E '^(user|group)' /etc/php/8.2/fpm/pool.d/www.conf

Een standaard Ubuntu-installatie van php8.2-fpm uit de package manager draait de pool als www-data UID 33. Een custom pool, een per-vhost pool, een Docker-container met de officiële wordpress:php8.2-fpm image, en een cPanel- of Plesk-installatie met suexec geven allemaal iets anders. Zoek de echte UID op voor je iets aanneemt.

Als je twee antwoorden overeenkomen, wordt het formulier door iets anders getriggerd (zie het stukje verderop over open_basedir en disable_functions). Als ze niet overeenkomen, heb je het meest voorkomende geval te pakken: de bestanden zijn van een deploy-gebruiker, en PHP draait als de webserver-gebruiker.

Fix 1: zet de eigenaar om naar de PHP-gebruiker

Dit is de architectonisch juiste fix. Hij pakt de oorzaak aan, verzwakt geen security, en het formulier komt niet meer terug.

Zodra je uit vraag 2 weet wie PHP draait, zet je de eigenaar van de WordPress-mapboom om naar die gebruiker:

# Verander eigenaar en groep van de hele WordPress-installatie
sudo chown -R www-data:www-data /var/www/html/

# Bevestig de wijziging
stat -c '%U %u %n' /var/www/html/wp-admin/includes/file.php

Herlaad nu Plugins > Nieuwe plugin in wp-admin en klik opnieuw op Nu installeren. Het formulier met Verbindingsgegevens is weg en de plugin installeert direct.

Je weet dat het werkt als de voortgangsbalk meteen verschijnt, er geen Verbindingsgegevens-formulier meer wordt getoond, en de nieuwe plugin binnen een paar seconden in Plugins > Geïnstalleerde plugins staat.

Eén kanttekening om eerlijk over te zijn: als je ook code naar deze map deploy't via een CI-pipeline, een SSH-sessie, of een SFTP-login als een andere gebruiker (meestal deployer of je eigen accountnaam), betekent het omzetten naar www-data dat die gebruiker zonder sudo geen bestanden meer kan schrijven. De nette oplossing is om zowel je deploy-gebruiker als www-data in een gedeelde groep te zetten, de directory mode op 775 te zetten, en de setgid-bit aan te zetten zodat nieuw aangemaakte bestanden de groep erven. Dat is dezelfde aanpak die WordPress beschrijft op de file-permissions pagina, en hij staat uitgebreid in mijn artikel over WordPress bestandsrechten.

Fix 2: zet FS_METHOD op direct in wp-config.php

Als je om welke reden dan ook de eigenaar niet kunt veranderen (managed host, dichtgetimmerde omgeving, deploy-pipeline die bij elke release de eigenaar overschrijft), kun je WordPress vertellen om de eigenaarscontrole helemaal over te slaan en gewoon direct file I/O te gebruiken.

Open wp-config.php in de bestandsbeheerder van je hostingpaneel (of download het via SFTP) en voeg deze regel toe boven het commentaar /* Dat is alles, stop met bewerken! Veel plezier met WordPress. */:

// Sla de get_filesystem_method() eigenaarscontrole over
// en gebruik direct file I/O voor plugin- en thema-installaties.
define( 'FS_METHOD', 'direct' );

Als FS_METHOD is gezet, geeft get_filesystem_method() die waarde meteen terug zonder de tijdelijke-bestand-eigenaarscontrole te draaien. WordPress gaat direct naar I/O en het FTP-formulier verschijnt niet meer.

Je weet dat het werkt als de volgende plugin-installatie zonder Verbindingsgegevens-formulier verloopt. Als de installatie afbreekt met een permission denied-fout in wp-content/plugins/, betekent dat PHP echt niet kan schrijven naar die map en is het echte probleem filesystem-rechten, niet de eigenaarscontrole. Dan is Fix 1 alsnog nodig.

De officiële wp-config.php documentatie is open over wanneer dit gevaarlijk is: hij waarschuwt dat FS_METHOD = 'direct' "fraught with opening up security issues on poorly configured hosts" is. Wat die waarschuwing eigenlijk betekent wordt vaak verkeerd geciteerd, dus even precies.

De waarschuwing geldt voor shared hosts waar elke klantsite PHP draait onder dezelfde systeemgebruiker, meestal apache of nobody. In die omgeving, als je direct mode forceert en je bestanden van die gedeelde gebruiker zijn, kan elk PHP-proces van elke andere klant je plugin-bestanden lezen en schrijven, en er is geen isolatie. Dat is écht slecht.

De waarschuwing geldt niet voor een single-site VPS, een dedicated server, een per-klant PHP-FPM pool, een Docker-container, een Kubernetes pod, of welke moderne managed host dan ook waar PHP onder een per-site of per-klant gebruiker draait. In die omgevingen is FS_METHOD = 'direct' gewoon de verwachte configuratie. De setting wordt samen met zijn broertjes uitgelegd in mijn gids over wp-config.php.

Vuistregel: als je hostingpakket "shared hosting" zegt zonder PHP-isolatie, blijf bij Fix 1 of bel je host. Als je hostingpakket een VPS, Docker, Kubernetes of een moderne managed WordPress-host is, is Fix 2 prima en meestal het simpelst.

Fix 3: relaxed file ownership

Er is een derde optie die minder is gedocumenteerd, en die het waard is om te kennen omdat de WordPress automatische background-updater hem gebruikt.

Binnen get_filesystem_method(), als de eigenaar van het tijdelijke bestand niet matcht, maar de functie is aangeroepen met $allow_relaxed_file_ownership = true, gebruikt WordPress alsnog direct I/O, alleen met een andere interne vlag (relaxed_ownership in plaats van file_owner). Je kunt dit niet globaal aanzetten vanuit wp-config.php, maar WP_Upgrader::fs_connect() geeft de vlag mee voor paden waar de background-updater op mag schrijven.

Het praktische gevolg is dat de WordPress background-updater stille minor core-updates en security patches kan installeren zonder het FTP-formulier, ook als handmatige installaties vanuit het dashboard er wel tegenaan lopen. Als je site 's nachts gewoon 6.7.2 installeert maar je krijgt het FTP-formulier zodra je op Nieuwe plugin klikt, is dit waarom.

Er is geen configuratie-constante voor het handmatige geval. De enige manier om relaxed ownership te benutten is om de upgrader API zelf aan te roepen met de vlag aan, en dat is plugin-territorium, niet iets wat je doet vanuit wp-config.php. Voor de meeste lezers is Fix 1 of Fix 2 het antwoord.

Veelvoorkomende Nginx- en PHP-FPM-scenario's op Ubuntu

Een paar specifieke setups dekken het overgrote deel van de gevallen die ik tegenkom.

Standaard Ubuntu Nginx + PHP-FPM, bestanden geüpload via SFTP als deployer. Bestanden zijn van deployer:deployer. PHP-FPM pool draait als www-data:www-data. UIDs matchen niet. Doe of chown -R www-data:www-data /var/www/html (Fix 1), of zet FS_METHOD = 'direct' (Fix 2). Op een single-site VPS zijn beide veilig.

Per-vhost PHP-FPM pools, één pool per klant. Dit is de standaard setup voor managed hosting met Nginx en PHP-FPM. Elke klant heeft een pool die als zijn eigen gebruiker draait (bijvoorbeeld customer42). Als de klant ook bestanden upload als customer42, matcht de eigenaar en verschijnt het formulier niet. Als de klant upload als een andere gebruiker (een developer-account, een deploy-bot), matcht het niet en verschijnt het wel. De oplossing is om de upload-gebruiker en de PHP-pool-gebruiker gelijk te maken, en dat is een instelling in het hostingpaneel, geen code-wijziging.

Docker, officiële wordpress:php8.2-fpm image. De image draait PHP-FPM als www-data UID 33. Als je je WordPress-bestanden via een bind mount naar /var/www/html zet vanaf de host, blijft de UID van de host-bestanden bewaard binnen de container. Als je host-UID 1000 is (typische Linux desktop-gebruiker), ziet de container die bestanden als eigendom van UID 1000, niet 33, en de eigenaarscontrole valt door. De officiële docker-library/wordpress repository heeft een al jaren lopende thread hierover in issue #298. De pragmatische fix in Docker is om de container te draaien als je host-UID (--user 1000:1000), of om FS_METHOD = 'direct' in de WordPress-config te zetten en de bind mount op de host éénmaal te chownen.

cPanel en Plesk shared hosts. Die draaien PHP meestal onder suEXEC of PHP-FPM met een per-account gebruiker. WordPress core-bestanden zijn van je accountgebruiker, PHP draait als diezelfde gebruiker, de eigenaarscontrole slaagt en het FTP-formulier verschijnt niet. Als je het ineens wel ziet op een host die het eerder niet deed, is de meest voorkomende oorzaak dat een recente migratie of restore de eigenaar heeft gewijzigd. De support van je host kan dat met één chown-commando rechttrekken.

Wanneer FTP wel de juiste filesystem-methode is

Er is precies één situatie waarin het invullen van echte FTP-gegevens (of die hard in wp-config.php zetten) het juiste antwoord is: wanneer PHP echt niet kan schrijven naar de WordPress-map en er geen pad is om dat mogelijk te maken. Sommige legacy shared hosts draaien zo, met opzet. Ze bieden een FTP-daemon als de enige schrijf-interface, en PHP heeft nooit schrijfrechten op de webcontent. In zo'n omgeving is FTP-mode geen workaround, maar de ondersteunde install-methode.

Als je in die situatie zit, ziet het credentials-blok in wp-config.php er zo uit, en het hoort thuis bij de andere wp-config.php instellingen:

define( 'FS_METHOD', 'ftpext' );
define( 'FTP_HOST', 'ftp.example.org' );
define( 'FTP_USER', 'username' );
define( 'FTP_PASS', 'password' );
define( 'FTP_BASE', '/path/to/wordpress/' );
define( 'FTP_CONTENT_DIR', '/path/to/wordpress/wp-content/' );
define( 'FTP_PLUGIN_DIR', '/path/to/wordpress/wp-content/plugins/' );
define( 'FTP_SSL', true );

Voor iedereen anders is FTP-mode een omweg die een blijvende afhankelijkheid op een aparte FTP-service introduceert, met zijn eigen fouten. Er is een bekende WordPress core-regressie die precies dat risico laat zien, en die is het waard om te kennen, ook al is het een ander probleem dan het FTP-formulier.

Gerelateerd maar anders: de WordPress 6.6 auto-updater regressie

Als je al FS_METHOD = 'ftpext' in wp-config.php hebt staan en je automatische background-updates breken met een PHP fatal error na de upgrade naar WordPress 6.6 of nieuwer op PHP 8.0 of hoger, dan kijk je naar een aparte, echte bug die niet hetzelfde is als het credentials-formulier dat dit artikel behandelt.

De bug staat in Trac ticket #62718. In WordPress 6.6 roept de auto-updater binnen WP_Upgrader zijn maintenance_mode() aan via WP_Filesystem() zonder argumenten, waardoor de FTP-verbinding nooit met credentials wordt geïnitialiseerd. Op PHP 8.0 en later krijgt de daaropvolgende ftp_nlist()-aanroep null als eerste argument en gooit PHP een fatal:

PHP Fatal error: Uncaught TypeError: ftp_nlist():
Argument #1 ($ftp) must be of type FTP\Connection, null given

De fix is meegegaan in WordPress 6.8.1 RC1 en latere releases. Als je vastzit op een oudere 6.6 of 6.7 en core niet kunt updaten, is de workaround om de filesystem-methode op direct te zetten (Fix 2 hierboven), zodat de kapotte FTP-codepad helemaal wordt vermeden.

Voor alle duidelijkheid: dit Trac-ticket heeft niets te maken met het eerste Verbindingsgegevens-formulier dat verschijnt zodra je op Nu installeren klikt. Dat formulier is de eigenaarscontrole uit get_filesystem_method(), dezelfde logica die er al jaren in zit. De Trac-bug is een aparte regressie die alleen mensen raakt die al voor FTP-mode hebben gekozen en op de background-updater leunen. Het zijn twee verschillende problemen met twee verschillende fixes, en je moet ze niet door elkaar halen.

Waarom chmod 777 niet het antwoord is

Zoekresultaten raden nog altijd chmod -R 777 wp-content/ aan als oplossing. Het is een slecht advies, en het is goed om te begrijpen waarom.

De eigenaarscontrole in get_filesystem_method() kijkt naar UIDs die fileowner() teruggeeft, niet naar mode-bits. De mode op 777 zetten verandert geen eigenaar. Het tijdelijke bestand wordt aangemaakt (want dat had het sowieso al gekund, omdat wp-content/ normaal beschrijfbaar is voor de webserver), de UIDs worden alsnog vergeleken, de vergelijking faalt alsnog, en WordPress valt alsnog door naar het FTP-formulier. De enige manier waarop chmod 777 het formulier "fixt" is via de relaxed-ownership-tak, en alleen als de upgrader-codepad die de functie aanriep $allow_relaxed_file_ownership op true had gezet. Voor een handmatige installatie via de admin UI wordt die tak niet gebruikt.

Wat chmod 777 wel zeker doet, is elk bestand in je wp-content-boom door iedereen schrijfbaar maken. De WordPress documentatie over file permissions is daar expliciet over: "If a malicious user can upload a PHP file to your site, they have complete control over your blog." Elk proces op dezelfde server, ook een gecompromitteerd PHP-proces van een andere site, kan een achterdeurtje neerzetten in je wp-content/uploads/. De juiste mode voor WordPress is 644 voor bestanden en 755 voor mappen, en het artikel over WordPress bestandsrechten legt het hele model uit.

Diagnosticeer de eigenaarsmismatch en pas Fix 1 of Fix 2 toe. Niet chmoddenen.

Andere dingen die het formulier kunnen triggeren

Als de eigenaars-UIDs wel matchen maar je het formulier toch ziet, zijn er twee minder voorkomende serverconfiguraties die hetzelfde symptoom veroorzaken doordat de tijdelijke schrijfactie zelf stilletjes mislukt.

open_basedir blokkeert de tempbestand-map. PHP's open_basedir-instelling beperkt welke mappen PHP mag lezen en schrijven. Als die niet de wp-content/ bevat die de upgrader als $context gebruikt, mislukt de fopen() op het tijdelijke bestand, draait de eigenaarsvergelijking nooit, en valt WordPress door naar FTP. Check je open_basedir-waarde in phpinfo() en bevestig dat het pad van je WordPress-installatie er binnen valt.

fileowner staat in disable_functions. Sommige hardened PHP-configuraties zetten POSIX-gerelateerde functies uit. Als fileowner() in de disable_functions-lijst staat, geeft function_exists('fileowner') binnen get_filesystem_method() false terug, blijven beide UIDs op false, en is false !== false evaluatie false, dus de direct-methode wordt nooit gekozen. Check disable_functions in je PHP-config en haal fileowner weg als hij erin staat.

Dit zijn allebei server-level instellingen. Als je host die beheert en weigert ze aan te passen, blijven alleen Fix 2 (FS_METHOD = 'direct') of overstappen naar een host met een redelijker PHP-config over.

Wanneer hulp inschakelen

Als geen van het bovenstaande het oplost, geef je het probleem aan je host of een WordPress-specialist met dit pakket informatie:

  • De exacte tekst van het formulier, inclusief eventuele placeholder-tekst in het host-veld (dat hint soms naar welk pad is genomen).
  • De output van stat -c '%U %u' /var/www/html/wp-admin/includes/file.php (of het equivalente pad op jouw installatie).
  • De output van ps -eo user,group,comm | grep php-fpm en grep -E '^(user|group)' /etc/php/8.2/fpm/pool.d/www.conf (paden aanpassen aan jouw PHP-versie).
  • De PHP-versie (php -v) en de WordPress-versie (zichtbaar onder Updates in het dashboard, of in wp-includes/version.php).
  • Je open_basedir en disable_functions uit phpinfo().
  • Of je al FS_METHOD = 'direct' in wp-config.php hebt gezet, en wat er gebeurde (de foutmelding doet ertoe: een permission denied is een ander probleem dan een stille mislukking).
  • Het hostingtype: VPS, dedicated, managed WordPress, shared met cPanel, Docker, Kubernetes.

Een host kan dit meestal in één chown-commando oplossen zodra die feiten op tafel liggen. Zonder die feiten wordt het support-ticket een raadspelletje.

Hoe je voorkomt dat het terugkomt

  • Houd je deploy-gebruiker en je PHP-gebruiker op één lijn. Welke gebruiker PHP ook draait op je server, dat hoort ook de gebruiker te zijn die de WordPress-bestanden bezit. Als je deploy't als deployer en PHP draait als www-data, verander dan of de deploy-gebruiker, of de PHP-gebruiker, of zet ze allebei in een gedeelde groep met de setgid-bit op directories.
  • Pin FS_METHOD = 'direct' in wp-config.php op geïsoleerde omgevingen. Op een VPS, Docker-container of Kubernetes pod waar je de PHP-gebruiker zelf bepaalt, zorgt deze constante éénmaal voor altijd dat de eigenaarscontrole nooit meer draait. Migraties en restores kunnen het formulier dan niet per ongeluk opnieuw triggeren.
  • Audit eigenaarschap na elke restore of migratie. Elk proces dat bestanden opnieuw aanmaakt (een backup-restore, een rsync vanuit staging, een Git-deploy die geen eigenaar bewaart) kan de UIDs onder WordPress vandaan trekken. Draai na elk van die acties hetzelfde stat-commando uit vraag 1 en bevestig dat de bestandseigenaar overeenkomt met de PHP-gebruiker, voordat iemand op Nu installeren klikt en het formulier opnieuw ontdekt.

WordPress onderhoud zonder omkijken?

Ik regel updates, backups en beveiliging, en houd performance strak—zodat storingen en traagheid niet terugkomen.

Bekijk WordPress onderhoud

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.