Created
June 4, 2026 03:11
-
-
Save vapvarun/ccb6d31565648f8aaca3a6a034e6593b to your computer and use it in GitHub Desktop.
WordPress shutdown action gotchas: headers, object cache, timeouts - tweakswp.com tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Common gotchas when using the WordPress shutdown action | |
| * | |
| * Covers: headers-already-sent, object cache availability, | |
| * timeout behavior, and output buffer state. | |
| * | |
| * Tutorial: https://tweakswp.com/ | |
| */ | |
| // --- Gotcha 1: Headers are already sent on shutdown --- | |
| // wp_redirect(), wp_die(), and header() will have no effect. | |
| // Do not call them on the shutdown action. | |
| add_action( 'shutdown', function () { | |
| // BAD — headers already sent: | |
| // wp_redirect( home_url() ); // This will trigger a "headers already sent" warning. | |
| // GOOD — write to storage instead of redirecting: | |
| update_option( 'my_plugin_last_run', time() ); | |
| } ); | |
| // --- Gotcha 2: Object cache may have been cleared --- | |
| // If your caching layer (Redis, Memcache) flushes at the end of the request, | |
| // wp_cache_get() on shutdown may return false even for keys you set earlier. | |
| // Always fall back gracefully. | |
| add_action( 'shutdown', function () { | |
| $cached = wp_cache_get( 'my_data_key', 'my_group' ); | |
| if ( false === $cached ) { | |
| // Cache is gone. Do a direct database read if needed. | |
| global $wpdb; | |
| $cached = $wpdb->get_var( "SELECT option_value FROM {$wpdb->options} WHERE option_name = 'my_option'" ); | |
| } | |
| // Use $cached safely. | |
| } ); | |
| // --- Gotcha 3: max_execution_time is not paused on shutdown --- | |
| // PHP's max_execution_time counter keeps running through shutdown callbacks. | |
| // If your request already took 28 seconds and max_execution_time is 30, | |
| // your shutdown callback has 2 seconds before a timeout fatal. | |
| // Extend it explicitly when running expensive tasks: | |
| add_action( 'shutdown', function () { | |
| set_time_limit( 60 ); // Reset the clock for the shutdown work. | |
| ignore_user_abort( true ); // Keep running if the browser closed the connection. | |
| // ... expensive background work ... | |
| }, 10 ); | |
| // --- Gotcha 4: Plugins hooked at shutdown priority 999 run after you --- | |
| // WordPress hooks run in priority order. Several caching plugins (e.g. W3TC, | |
| // WP Super Cache) flush their page cache buffer at 'shutdown' with late priorities. | |
| // If your code depends on the response being fully assembled, use a late priority. | |
| add_action( 'shutdown', 'tweakswp_late_cleanup', 100 ); | |
| function tweakswp_late_cleanup(): void { | |
| // Runs after most plugins have finished their shutdown work. | |
| } | |
| // --- Gotcha 5: $wpdb connection may close before shutdown completes --- | |
| // On high-load servers, the MySQL connection can time out during a long | |
| // shutdown callback. Re-check the connection if you run queries after heavy work. | |
| add_action( 'shutdown', function () { | |
| global $wpdb; | |
| // ... do long work first ... | |
| // Re-ping if queries might follow after a long delay: | |
| if ( $wpdb->check_connection() ) { | |
| $wpdb->insert( $wpdb->prefix . 'my_log', [ 'event' => 'done', 'ts' => time() ] ); | |
| } | |
| } ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment