Skip to content

Deployment · Open source

Kiln

Artisan commands and an optional signed HTTP route to clear, warm and inspect OPcache when you deploy.

Laravel 12 and 13 PHP 8.3+ v1.0.0 Free · MIT
Kiln: Artisan commands and an optional signed HTTP route to clear, warm and inspect OPcache when you deploy.

What it solves

With opcache.validate_timestamps=0, the recommended production setting, a Laravel app keeps serving stale bytecode after a deploy until the process pool restarts.

Resetting OPcache from php artisan does not fix it, because the CLI and the FPM worker pool do not share the same OPcache.

Kiln adds kiln:clear, kiln:warm, kiln:status and kiln:config, plus an optional route that runs the clear or warm inside an FPM worker. The route only accepts temporary signed URLs, and clear and warm are POST-only.

  • Four Artisan commands: kiln:clear, kiln:warm, kiln:status and kiln:config
  • kiln:warm compiles files with opcache_compile_file(), which does not run their top-level code
  • Optional HTTP route that clears or warms OPcache inside an FPM worker
  • The route refuses any signed URL without an expiry, even if the signature is valid
  • clear and warm are POST-only, so link prefetchers and scanners cannot trigger them
  • Warming over HTTP stops after 20 seconds by default and reports partial progress

Install

Requires PHP 8.3+ and Laravel 12 and 13.

composer require amjadiqbal/kiln

Usage

// .env: KILN_ROUTE_ENABLED=true
use Illuminate\Support\Facades\URL;

$url = URL::temporarySignedRoute('kiln.clear', now()->addMinutes(5));

// Then, from your deploy script: curl -fsS -X POST "$url"

Why Kiln exists

appstract/laravel-opcache did this job for years, but its last release, 4.0.2, shipped in December 2020. It requires guzzlehttp/guzzle: ^6.3.1|^7.0, and a fresh Laravel 13 app ships Guzzle 8, so 4.0.2 cannot be installed there. A plain composer require appstract/laravel-opcache on Laravel 13 does not fail, though: Composer quietly falls back to 2.0.5 (December 2019) plus appstract/lush-http, which Packagist lists as abandoned. On Laravel 12 (Guzzle 7) 4.0.2 still installs. Kiln is a from-scratch replacement for PHP 8.3+ and Laravel 12/13. It sets no Guzzle constraint of its own.

Setup

Package auto-discovery registers the service provider. Publish the config to change the warmed paths or enable the HTTP route.

  1. 1 composer require amjadiqbal/kiln
  2. 2 php artisan vendor:publish --tag=kiln-config

Artisan commands

A php artisan process has its own OPcache, separate from the FPM worker pool's. Run from the CLI, kiln:clear and kiln:warm only affect the CLI's OPcache and print a warning saying so. --time-limit bounds a warm run; on the CLI there is no limit by default.

php artisan kiln:clear     # opcache_reset() in this process
php artisan kiln:warm      # compile every file under config('kiln.warm_paths') into OPcache
php artisan kiln:status    # hit rate, memory usage, cached script count
php artisan kiln:config    # dump the relevant opcache.* ini directives, flag risky settings

php artisan kiln:warm --time-limit=15

The HTTP route for FPM deploys

Set KILN_ROUTE_ENABLED=true to register POST /kiln/opcache/clear, POST /kiln/opcache/warm and GET /kiln/opcache/status. Every route needs a temporary signed URL; a URL made with URL::signedRoute() has no expiry and is refused. Clearing once resets the shared OPcache for every worker in that pool. warm is bounded by kiln.warm_time_limit (20 seconds by default) and reports "timed_out": true with partial progress when it hits the limit.

url=$(php artisan tinker --execute="echo URL::temporarySignedRoute('kiln.clear', now()->addMinutes(5));")
curl -fsS -X POST "$url"

Behind a reverse proxy

If every call returns 403 Invalid signature although the URL and APP_KEY are correct, the app is probably not seeing the public scheme and host, usually because TrustProxies is not set up. Fix TrustProxies first. Otherwise set KILN_ABSOLUTE_SIGNATURE=false and generate every URL with absolute: false; the flag must match on both ends.

URL::temporarySignedRoute('kiln.clear', now()->addMinutes(5), [], absolute: false)

Hire me

Need this built into your app, or something custom?

I wrote Kiln. I can wire it into your Laravel app, extend it for your case, or build the feature around it. Tell me what you need and you'll get a plan and a quote, free.

Support

Found a bug or want a feature? Open an issue on GitHub. For help adding it to your own project, use the quote form above.

More Laravel packages

All packages
Laravel Driver.js: Build Driver.js product tours in PHP and render them with one Blade component.

Laravel Driver.js

Build Driver.js product tours in PHP and render them with one Blade component.

Laravel Sonner: Sonner toast notifications for Laravel, sent from PHP and shown without React.

Laravel Sonner

Sonner toast notifications for Laravel, sent from PHP and shown without React.

Laravel Ninja Keys: A Cmd+K / Ctrl+K command palette for Laravel, with the actions defined in PHP.

Laravel Ninja Keys

A Cmd+K / Ctrl+K command palette for Laravel, with the actions defined in PHP.

Laravel SweetAlert2: SweetAlert2 popups, toasts and confirm dialogs from Laravel controllers, Livewire and Alpine.

Laravel SweetAlert2

SweetAlert2 popups, toasts and confirm dialogs from Laravel controllers, Livewire and Alpine.

Laravel Grid.js: Turn any Eloquent query into a searchable, sortable, paginated Grid.js data table with one fluent PHP call.

Laravel Grid.js

Turn any Eloquent query into a searchable, sortable, paginated Grid.js data table with one fluent PHP call.

Laralink: Work on a Composer package inside the Laravel app that uses it, then switch back to the Packagist release.

Laralink

Work on a Composer package inside the Laravel app that uses it, then switch back to the Packagist release.

Refresher: A drop-in replacement for Laravel's RefreshDatabase that restores a cached snapshot of your test database instead of running migrate:fresh.

Refresher

A drop-in replacement for Laravel's RefreshDatabase that restores a cached snapshot of your test database instead of running migrate:fresh.

Laravel Media Viewer: Show private PDFs, video and audio from any Laravel disk without making the files public.

Laravel Media Viewer

Show private PDFs, video and audio from any Laravel disk without making the files public.

Laravel Swiper: Swiper sliders, carousels and image galleries for Laravel as Blade components.

Laravel Swiper

Swiper sliders, carousels and image galleries for Laravel as Blade components.

Laravel MySQL Advisor: Read-only health checks for the MySQL or MariaDB database behind your Laravel app, with the SQL and the Laravel migration to fix a problem wherever a fix applies.

Laravel MySQL Advisor

Read-only health checks for the MySQL or MariaDB database behind your Laravel app, with the SQL and the Laravel migration to fix a problem wherever a fix applies.

Laravel Webhook Console: A dashboard and replay tool for the webhooks spatie/laravel-webhook-client stores in your Laravel app.

Laravel Webhook Console

A dashboard and replay tool for the webhooks spatie/laravel-webhook-client stores in your Laravel app.

Laravel Pennant Dashboard: A browser dashboard for laravel/pennant feature flags: per-scope toggles, sticky percentage rollouts, purge and an audit log.

Laravel Pennant Dashboard

A browser dashboard for laravel/pennant feature flags: per-scope toggles, sticky percentage rollouts, purge and an audit log.

Laravel Notification Inbox: Catches every non-mail Laravel notification in local and staging instead of sending it, and shows each one as it would look on the other end.

Laravel Notification Inbox

Catches every non-mail Laravel notification in local and staging instead of sending it, and shows each one as it would look on the other end.

Laravel WebPush Kit: The browser side of Laravel web push: a subscribe button with a two-step permission prompt, a service worker, and routes that save each user's subscription. Tested in Chrome so far.

Laravel WebPush Kit

The browser side of Laravel web push: a subscribe button with a two-step permission prompt, a service worker, and routes that save each user's subscription. Tested in Chrome so far.

Laravel Redis Advisor: Read-only health checks for the Redis or Valkey behind your Laravel cache, queues, sessions and Horizon, with the redis.conf line or Laravel config that fixes each problem.

Laravel Redis Advisor

Read-only health checks for the Redis or Valkey behind your Laravel cache, queues, sessions and Horizon, with the redis.conf line or Laravel config that fixes each problem.

Need something built?

Free quote in 60 seconds