whoami

Lucas Jenß

cat /etc/motd

The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.

Close-up of white dominoes with black dots standing on green felt surface, shallow depth of field, focused mood

ls -la ~/languages/

total 8
drwxr-xr-x
▶ PHP
▶ Ruby
▶ Scala
▶ C#
▶ JavaScript
▶ Objective-C
▶ Shell Scripting

ls -la ~/toolchain/

total 7
drwxr-xr-x
▶ Typo3
▶ Akka
▶ Capistrano
▶ Git
▶ MAMP
▶ Adobe Illustrator
▶ NSTrackingArea (Cocoa)

uname -a

platforms
drwxr-xr-x
▶ Mac OS X
▶ Unix

Run TYPO3 commands in cron with the right environment

A TYPO3 command can work perfectly in an SSH session and fail when moved to cron. The usual cause is not cron itself but the environment it creates: a minimal PATH, a different working directory, no interactive shell profile, and sometimes a different HOME or timezone. TYPO3 also needs the Composer installation and application configuration before its console kernel can bootstrap.

This matters to Australian teams hosting TYPO3 in Sydney or Melbourne, where deployments may target a managed VPS, a cPanel or Plesk server, or a container platform. A deployment user can have the right files while the cron daemon still starts with an unsuitable PHP binary. The command name is only the final layer; project root, PHP extensions, dotenv values, database credentials, cache paths and TYPO3 context all have to line up.

The dependable approach is to put the command in a small wrapper, run that wrapper as the deployment user, and add only a short cron entry. Testing must reproduce cron’s limited environment rather than rely on the developer’s shell. This pattern applies to scheduler:run, cache:warmup, a database update or a custom maintenance command while keeping console invocation separate from scheduling policy.

Why cron changes the TYPO3 runtime

Cron normally changes directory to the account’s home unless the entry changes it. It also receives a reduced environment and does not load .bashrc or .profile. A Composer project is therefore easy to break in two ways: vendor/bin/typo3 may not be found, or TYPO3 may bootstrap from the wrong root and look for missing configuration. A migrated host can add another variable: VMware vMotion CPU failures describes how an infrastructure move exposed an incompatibility hidden on the original host, a useful reminder to compare PHP builds and loaded extensions after infrastructure changes.

The runtime differences are predictable:

Runtime detail Interactive terminal Cron entry
Working directory Usually the project root Often the user’s home
PHP executable Resolved through the shell PATH May resolve differently
Environment Shell exports and profiles Daemon defaults only
Home and temporary paths Login user’s values Account-dependent values
Timezone User and PHP settings Daemon and PHP settings may differ

Change to the Composer project root before invoking vendor/bin/typo3; otherwise the autoloader and configuration lookup can fail. Use an absolute PHP path such as /usr/bin/php8.3, because cron’s PATH may differ from an SSH login. The same command can then behave consistently on a local workstation, a CI worker and an Australian production host.

Time settings need explicit attention as well. A server in Sydney moves between AEDT and AEST, while a Brisbane host stays on AEST. The scheduler evaluates due work against the effective application time, so an incorrect PHP timezone can make jobs appear early, late or not due. Record the server’s zone with timedatectl, check PHP’s configured timezone, and decide whether business schedules should follow Sydney local time, Brisbane local time or UTC before enabling the job.

Build a command wrapper for the deployment

Create a wrapper beside the deployment scripts and make it executable. Keep environment defaults explicit, but leave secrets to TYPO3’s environment-file or secret-provider mechanism. This example runs any TYPO3 console command passed to it:

#!/usr/bin/env bash
set -Eeuo pipefail
umask 027

APP_ROOT="/srv/typo3/example-site"
PHP_BIN="/usr/bin/php8.3"

export HOME="${HOME:-/home/deploy}"
export APP_ENV="${APP_ENV:-prod}"
export TYPO3_CONTEXT="${TYPO3_CONTEXT:-Production}"

cd "$APP_ROOT"
exec "$PHP_BIN" vendor/bin/typo3 "$@"

Replace the paths with the values on the target host, and use command -v php to identify the installed binary. Australian Ubuntu servers may provide several PHP versions, so assuming php8.3 can select the wrong runtime. The wrapper’s working directory lets Composer and TYPO3 find vendor/ and the application configuration, while exec preserves the command’s exit status instead of hiding it behind another shell.

Run the wrapper as the Unix user that owns the release. Using root may create root-owned cache files that the web server cannot replace later. A common Australian hosting custom is to keep production under a deploy account while panel users create panel-level cron jobs; map the panel job to this wrapper and grant write access only to the required log and lock directories. Test it with a deliberately limited environment:

sudo -u deploy env -i HOME=/home/deploy PATH=/usr/bin:/bin \
  /srv/typo3/example-site/bin/run-typo3 list

Use the hosting provider’s user-switch command if sudo is unavailable. Run list first to confirm that TYPO3 loads and discovers the expected commands, then execute the real command. A successful cache:warmup can also reveal whether cache directories are writable, while scheduler:run will expose database, site configuration and scheduler problems.

Confirm environment values and filesystem paths

TYPO3 loads environment-specific settings through its supported dotenv and configuration mechanisms, but precedence matters. A value exported by the wrapper can override a repository .env file, which is useful for APP_ENV and TYPO3_CONTEXT but risky for secrets. Keep APP_SECRET and database credentials in deployment-restricted storage and ensure the selected context can read them. Never paste secret values into a crontab because crontabs and process arguments may be readable by other accounts.

For a command supplied by a custom extension, first prove that its service is registered and its configuration is available. The implementation in a custom scheduler task demonstrates the task and configuration path for a cleanup job. The same registration requirements apply when that task is triggered through the CLI. A wrapper cannot compensate for a missing extension, an invalid site identifier or stale Composer autoload files.

Confirm these runtime values without printing their secret contents:

  • APP_ENV=prod selects the Symfony runtime profile used by the release.
  • TYPO3_CONTEXT=Production enables production TYPO3 behaviour rather than development tooling.
  • APP_SECRET must be available to features requiring signed or encrypted state.
  • TYPO3_DB_* values must identify the same database as the web application when the deployment uses them.

Then confirm the filesystem paths:

  • vendor/bin/typo3 and vendor/autoload.php must belong to the installed release.
  • The config/ directory and site-package configuration must be readable.
  • var/cache/ and var/log/ must be writable by the deployment user.
  • Dotenv files selected for the release must exist and protect any sensitive values.

Schedule the job with the right clock

Once the wrapper succeeds manually, schedule the wrapper rather than Composer or a long inline shell command:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/deploy

*/5 * * * * /srv/typo3/example-site/bin/run-typo3 scheduler:run >> /srv/typo3/example-site/var/log/typo3-cron.log 2>&1

Cron normally sends command output to local mail, so explicit redirection keeps failures in a predictable location. Protect and rotate that log because it may contain file paths, identifiers or exception details. The crontab contains no application secrets; the wrapper retrieves them through the deployment’s protected configuration mechanism.

A long-running command can also overlap with its next scheduled execution. On Linux, flock provides a simple guard:

*/5 * * * * flock -n /srv/typo3/example-site/var/lock/typo3.lock /srv/typo3/example-site/bin/run-typo3 scheduler:run >> /srv/typo3/example-site/var/log/typo3-cron.log 2>&1

Create the lock directory with suitable ownership before enabling this entry. If the host does not provide flock, add an equivalent lock inside the wrapper or use TYPO3’s own scheduler protection.

Cron interprets schedules in the daemon’s timezone, which need not match PHP or the business location. Adelaide changes between ACST and ACDT and sits half an hour behind Sydney during daylight saving, while Perth remains on AWST without daylight saving. Avoid assuming that every cron implementation handles a skipped or repeated wall-clock time identically. Check date, timedatectl and PHP’s timezone, and make the daemon and application agree before relying on an exact daily schedule.

Deploy, verify and make failures visible

Deploy in a controlled order: install Composer dependencies as the release owner, warm the cache, test the wrapper as the deployment user, and only then enable cron. A cron entry should never run composer install or an extension setup command concurrently with live traffic because an interrupted installation can leave the application inconsistent. Keep the wrapper in version control while storing site-specific values and secrets outside the repository.

When a job fails, compare the wrapper, PHP runtime and TYPO3 configuration separately. Run the command with increased console verbosity, inspect the cron log and confirm that the process exit status is non-zero. This explicit failure signalling follows the same useful principle described in actor failure detection strategies: monitoring works when the failed component reports a clear signal rather than leaving the operator to infer success from silence. Send cron failures to the existing alerting system and test that alert before production use.

Create and commit the wrapper with the deployment repository, execute it once as the deployment user, confirm the expected command and exit code, and then enable the locked cron entry. Keep secrets out of the crontab, rotate the job log and verify the monitoring path before declaring the schedule operational.


cat ~/interests.json

KeyValue
editorTerminal-first workflow
osMac OS X / Unix
vcsGit, distributed version control
deployCapistrano, cron automation
graphicsSVG, Adobe Illustrator troubleshooting
networkingIP validation, SSH, VPN

git log --oneline --reverse

2013-10-30

Solving SVG import issues in Adobe Illustrator CS6 and CC

When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.

2013

Solving NDK build issues on OS X

Troubleshooting native development kit compilation problems on Mac OS X.

2013

Programmatically adding PHP generated TypoScript to the backend configuration

Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.

2013

ArgumentError: Could not parse PKey: no start line

Debugging an SSH key parsing error encountered during deployment.

2011-08-04

Validating IP-Addresses in PHP

Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.

2011-07-09

Cocoa: Using NSTrackingArea

A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.


cat ~/contact.txt

github: github.com/x3ro
stackoverflow: x3ro
coderwall: coderwall.com/x3ro
twitter: @x3rames