← Blog

Operations

Cron jobs via FTP: een overgenomen server in kaart brengen

Een FTP-login, geen SSH, en een klant die zegt 'er draait 's nachts iets'. Zo breng je elke cron job op een verouderde server in kaart vóór migratiedag.

Jacob Molkenboer· Oprichter · A Brand New Company· 19 feb 2024· 6 min
Messing zakhorloge, gevouwen rooster met touw, groen label aan ijzeren sleutel, rode wasstempel op ivoor.

Je neemt een vijf jaar oude PHP-site over voor migratie. De vorige developer is onbereikbaar. De klant geeft je een FTP-login, een MySQL-login, en één zin: 'er draait 's nachts iets, breek het alsjeblieft niet.' Geen SSH. Geen wachtwoord voor het hostingpaneel. Geen documentatie. Dat 'iets' is een cron job, of meerdere, en je hebt geen idee wat ze doen. Welkom in de meest voorkomende startsituatie van legacy werk.

Deze post gaat over hoe je uitvindt wat er werkelijk draait op die server voordat je iets aanraakt. Het risico van gokken is reëel. Cron-taken versturen facturen, syncen voorraad, laten sessies verlopen, proberen mislukte betalingen opnieuw. Verhuis de site zonder ze in kaart te brengen en je levert twee dagen na launch een stille storing op.

Waar je daadwerkelijk toegang toe hebt

Alleen FTP betekent niet blind. Het betekent dat je elk bestand onder de home directory van de FTP-gebruiker kunt lezen, en meestal niets anders. Je kunt crontab -l niet uitvoeren. Je kunt /etc/cron.d niet listen. Je kunt journalctl niet tailen. Maar de server schrijft al jaren naar zijn eigen home directory, en die geschiedenis is een transcript van wat er draait en wanneer.

Voordat je begint te graven, doe één goedkoop ding. Open de loginpagina van het hostingaccount (cPanel, Plesk, DirectAdmin, ISPConfig, custom) en vraag de klant om het wachtwoord te resetten. De helft van de tijd hebben ze nog toegang en zijn ze dat vergeten. Met paneeltoegang is de rest van deze post een klus van een half uur. Zonder paneeltoegang, twee uur.

Waar cron woont op shared hosting

Echte crontab-bestanden staan in /var/spool/cron/ en die kun je niet lezen via FTP. Wat je wel kunt lezen, is alles wat de cron-service in de home directory aanraakt: mirrors, logs, output redirects, lock files, mail spools. Elk paneel heeft zijn eigen eigenaardigheden.

  • cPanel: mail van cron belandt in ~/mail/cur/ als Maildir-entries. Elke cron job die output produceert mailt de gebruiker, en cPanel bewaart die mails. Ze hebben een timestamp. Elk bericht begint met de commandline die het produceerde. Lees de laatste 30 dagen en je hebt het schema in handen.
  • Plesk: vergelijkbare mail spool, plus logs per domein in ~/logs/. Plesk schrijft op sommige configuraties ook een paneelmirror van geplande taken in ~/.psa/.
  • DirectAdmin: ~/.shadow/cron/ is soms leesbaar en bevat de letterlijke crontab.
  • Custom of geen paneel: kijk in ~/.bash_history, ~/.lesshst, ~/.mysql_history. Ze staan vol aanwijzingen over wat de vorige developer met de hand draaide en later in een job omzette.

De eigen documentatie van cPanel is vijf minuten doorbladeren waard als je dit niet eerder hebt gedaan. De cron jobs interface reference noemt de exacte paden waar het paneel naartoe schrijft.

Luister naar het filesystem als naar een klok

Het filesystem zelf is een klok. Bestanden die elke maandag om 03:14 worden gewijzigd, vertellen je dat er een job draait op maandag 03:14. Je hoeft nog niet te weten wat hij doet, alleen dat hij een vingerafdruk heeft.

Plaats een markerbestand op een bekend tijdstip, wacht 24 uur, en list dan alles wat daarna gewijzigd is. De meeste FTP-clients tonen wijzigingstijden. Doet die van jou dat niet, mount de FTP-root lokaal met curlftpfs of lftp:

# mount de FTP-root lokaal
mkdir -p /tmp/srv
curlftpfs ftp.example.com /tmp/srv -o user=login:password

# zet een marker op een bekend tijdstip
touch -d "2026-06-03 12:00" /tmp/marker

# 24 uur later, list alles wat de server sindsdien geschreven heeft
find /tmp/srv -type f -newer /tmp/marker 2>/dev/null | sort

Wat eruit komt, zijn de bewegende delen van de site. Logs die roteren. Cache files die opnieuw worden opgebouwd. Rapporten die worden gegenereerd. Elk daarvan is downstream van een geplande taak. Groepeer ze per directory en het patroon valt snel op.

De praktijksituatie: HTTP-getriggerde crons

Op shared hosting is de meeste 'cron' helemaal geen cron. Het is een curl- of wget-request die de host op een schema afvuurt op een URL, omdat shell jobs beperkt of extra betaald zijn. Of het is de eigen interne scheduler van de applicatie, aangeroepen door een externe pinger.

Zoek dus naar endpoints, niet naar scripts. De patronen die je tegenkomt:

  • /cron.php, /scheduler.php, /run.php, /tasks.php in de web root
  • /wp-cron.php op WordPress (zie de officiële WP-Cron referentie voor wat wanneer afgaat, en waarom de meeste productiesites de loopback-versie uitzetten)
  • /cron/cron.php of /?q=cron op Drupal 7
  • /cron.php?key=... op Drupal 9 en 10
  • Een wrapped bin/magento cron:run op Magento 2
  • Een custom endpoint beschermd door een .htaccess IP-allowlist. Lees die .htaccess-bestanden. Ze vertellen je vanaf welke IP's de cron-service belt.

Heb je access logs, ook al zijn ze gegzipt en weggestopt in ~/logs/, grep ze op die paden. De User-Agent verraadt meestal de trigger. cPanel jobs komen binnen met Wget/1.x. UptimeRobot-achtige externe pingers komen binnen met hun eigen UA. WordPress loopback cron komt binnen met WordPress/X.Y.

Waarschuwing

Een geërfde cron die een remote script binnenhaalt met curl | sh of wget -O - | bash is een actieve achterdeur. Behandel zo'n job als gecompromitteerd tot het tegendeel bewezen is. Roteer elke secret op de machine voordat je de DNS omzet.

Het schema reconstrueren dat je nooit hebt gekregen

Je hebt inmiddels drie datasets: vingerafdrukken van wijzigingstijden op het filesystem, transcripts uit de mail spool, en hits in de access logs. Combineer ze. Een bestand op ~/var/exports/orders-YYYYMMDD.csv dat elke dag om 04:05 wordt gewijzigd, plus een cron-mail om 04:05 met onderwerp 'orders export OK', plus een access-log entry voor /cron/export-orders.php?token=... om 04:05, is dezelfde job, vanuit drie hoeken bekeken.

Schrijf het schema uit in een gewoon crontab-formaat, ook als je het zo nooit kunt deployen. Je bouwt aan de migratiespec.

# gereconstrueerd uit filesystem + mail spool + access logs
# 2026-06-03 door de nieuwe beheerder

5  4 * * *   /usr/bin/php /home/site/cron/export-orders.php
*/15 * * * * curl -s https://site.example/wp-cron.php?doing_wp_cron
0  3 * * 1   /usr/bin/php /home/site/cron/weekly-invoice-chase.php
30 2 * * *   /usr/bin/php /home/site/cron/clear-sessions.php

Dag één van een echte migratie

Toen we dit voorjaar een FTP-only Drupal 7 site oppakten voor een Nederlandse groothandel, was de enige documentatie van de klant een Post-it met de tekst 'cron draait'. De eerste drie uur waren precies het werk hierboven: FTP-root mounten, marker plaatsen, mail spool lezen, access logs grepen. We vonden elf geplande jobs. Twee daarvan faalden al veertien maanden in stilte. Eén verstuurde een onversleutelde CSV met klantadressen naar een Gmail-account dat niet meer bestond.

Dat soort dood gewicht is normaal op overgenomen infrastructuur, en het is de reden dat we de cron-audit altijd vóór de code-audit doen bij een legacy migratie. De site die je gaat verhuizen, is zelden de site die je dénkt te verhuizen.

Het kleinste wat je vandaag kunt doen: open je FTP-client, sorteer de home directory aflopend op wijzigingsdatum, en lees de bovenste vijftig bestanden. De helft van de geheimen van de server staat in die lijst.

Kern

Geërfde cron jobs laten vingerafdrukken achter op het filesystem, in de mail spool en in de access logs. Vind ze vóór de migratie, of je levert een stille storing op.

FAQ

Kun je echt cron jobs in kaart brengen zonder shell-toegang?

Ja. Cron schrijft mail, logs, outputbestanden en lock files naar de home directory van de FTP-gebruiker. Door 24 uur te kijken wat er gewijzigd wordt, krijg je het schema in beeld, ook zonder crontab-toegang.

En als het hostingpaneel helemaal geen cron-sectie heeft?

Dan zijn de crons vrijwel zeker HTTP-getriggerd. Zoek naar /cron.php-achtige endpoints, check .htaccess op IP-allowlists, en grep de access logs op terugkerende requests met een vast ritme.

Hoe lang moet de marker-en-wacht audit lopen?

Minimaal 24 uur voor dagelijkse jobs, idealiter 8 dagen zodat je ook weekschema's vangt. Maandelijkse jobs vragen een volle maand, of een aanwijzing uit de cron-mail in de spool.

Is wp-cron eigenlijk wel een cron job?

Nee. WP-Cron gaat af bij pageviews en doet alsof hij gepland is. Op sites met weinig verkeer pingt een echte cron elke 15 minuten wp-cron.php om hem voorspelbaar te maken. Die ping vind je terug in de access logs.

Wat moet ik roteren voordat de gemigreerde site live gaat?

Elke API-key, SMTP-wachtwoord, database-wachtwoord en webhook secret die ergens in de cron-scripts genoemd wordt. Behandel de oude machine als onvertrouwd vanaf het moment dat je hem overneemt.

legacy sitesmigrationoperationsphpwordpresstooling

Iets bouwen?

Start een project