TimThumb sat in almost every commercial WordPress theme around 2010. Drop one PHP file in the theme, pass src, w, and h on the query string, and you got a cropped JPEG without touching the Media Library. That convenience is why the script spread, and why the 2011 remote code execution wave hit so many sites at once.
In 2026 there is no reason to keep it. WordPress writes intermediate sizes at upload, browsers pick a width from srcset, and CDNs can transform a master file at the edge. If timthumb.php (or a renamed thumb.php) still lives under wp-content, treat it as an open door, not a feature.
Why TimThumb died
TimThumb did not fail because cropping went out of fashion. It failed because the design assumed a theme could safely fetch, resize, and cache images from URLs while running as PHP on the same host that served the site.
The 2011 flaw let attackers abuse writable cache paths and weak validation so the script wrote executable content the web server would later run. Patch waves followed, then forks, then silence. Themes kept shipping copies under new filenames. Scanners still find them on inherited projects years later because nobody owned the cleanup once the marketing site launched.
Three structural problems never went away:
- Security. A public PHP endpoint that accepts a remote or local
srcand writes under a cache directory is a classic RCE surface when path checks slip. - Performance. First request paid a PHP and GD (or Imagick) tax. Cache misses under traffic turned the origin into an image factory.
- No Media Library contract. TimThumb ignored attachment metadata. WordPress could not track crops, regenerate after a theme change, or emit responsive markup from a single source of truth.
Core media closed that gap. Uploads land under /wp-content/uploads/, WordPress records width and height, and registered sizes become real files next to the original. Display code reads those files. Nothing in the theme needs to open a URL, resize it, and hope the cache directory stays locked down.
If you are auditing a client handoff, treat any theme-level resize endpoint the same way you treat an open eval on $_GET: remove it, then prove images still resolve through attachments. Security scanners flag TimThumb by filename; attackers also probe renamed copies, so filename hygiene alone is not enough.
Core image sizes
WordPress ships default intermediates (thumbnail, medium, medium_large, large) and lets themes register more with add_image_size(). Those are the replacements for TimThumb query strings: declare the crops your layouts need, generate them once, serve static files forever after.
Register sizes on after_setup_theme:
function wppoland_setup_theme() {
add_theme_support( 'post-thumbnails' );
// Soft crop - keep aspect ratio, fit inside the box.
add_image_size( 'blog-list', 800, 400 );
// Hard crop - exact box from the center.
add_image_size( 'team-member', 300, 300, true );
// Hard crop with focus toward the top of the frame.
add_image_size( 'hero-banner', 1920, 600, array( 'center', 'top' ) );
}
add_action( 'after_setup_theme', 'wppoland_setup_theme' );In a template, prefer the attachment helpers so WordPress can attach classes, alt text, and responsive attributes:
if ( has_post_thumbnail() ) {
the_post_thumbnail( 'hero-banner', array( 'class' => 'img-fluid' ) );
}Or, when you already hold an attachment ID:
echo wp_get_attachment_image( $attachment_id, 'blog-list' );Sizes registered after files were uploaded do not appear until you regenerate. On a small library the Regenerate Thumbnails plugin is enough. On anything large, use WP-CLI so the job can run without a browser timeout:
wp media regenerate --yesKeep the size list short. Every extra crop multiplies disk use. Register what the theme and content models actually render - list cards, author avatars, heroes - and drop dead names when a redesign retires a layout. You can inspect registered intermediates with wp_get_intermediate_image_sizes() when auditing a theme you did not write.
The win versus TimThumb is simple: generation happens at upload (or regenerate) time. Page views serve files from disk or CDN cache. Origin PHP is not in the resize path.
Editors should still upload a high-quality master that is large enough for the biggest crop you register. Soft crops cannot invent missing pixels. Hard crops need composition awareness: a headshot registered as center, top behaves differently from a landscape product shot cropped from the dead center. Document those choices in the theme README so the next developer does not reintroduce a TimThumb-style “just pass w and h” shortcut.
Srcset and the sizes attribute
Modern WordPress does not stop at one derivative. When you print an image through wp_get_attachment_image() or the_post_thumbnail(), core builds a srcset from available widths and a default sizes hint. The browser then downloads a file close to the display width instead of always pulling the hero crop on a phone.
Rough shape of what core emits:
<img
src="image-800x400.jpg"
srcset="image-300x150.jpg 300w,
image-800x400.jpg 800w,
image-1024x512.jpg 1024w"
sizes="(max-width: 600px) 100vw, 800px"
alt=""
/>Your job as a theme developer is usually not to hand-write srcset. It is to:
- Register sensible intermediates so the set has useful steps.
- Pass an accurate
sizesvalue when the default is wrong for a layout (full-bleed hero versus a third-column card). - Filter
wp_calculate_image_sizeswhen a component always sits in a known CSS width.
TimThumb could not do this. It produced one URL per request. Responsive delivery depended on either shipping oversized files or inventing more query-string variants. Core metadata plus srcset is the maintained path, documented with the rest of WordPress media administration.
For LCP images, still be explicit: preload or mark the hero with fetchpriority="high" where appropriate, and avoid lazy-loading the first viewport image. Responsive markup helps bandwidth; it does not replace choosing a sane primary src.
When debugging “why is the phone still downloading the 1920px file,” inspect the rendered sizes attribute first. A wrong 100vw on a 320px-wide card forces the browser to pick a huge candidate even when smaller intermediates exist. Fix the hint; do not bring back a theme PHP resizer to paper over bad markup.
Modern CDN transforms
Native sizes cover most editorial layouts. Design systems, ad slots, and A/B heroes sometimes need widths you never registered. Putting that work back into a theme PHP script recreates TimThumb. Push it to the CDN instead.
Hosts and edge platforms commonly accept width, height, fit, and format on a URL or via an image worker in front of /wp-content/uploads/. Conceptually:
<img
src="https://cdn.example.com/uploads/2026/09/hero.jpg?width=800&height=400&format=avif"
alt=""
width="800"
height="400"
/>The origin keeps one master (or the WordPress full size). The edge caches each transform. You keep security boundaries: the CDN only reads objects you already published under uploads, not arbitrary remote src values from a query string on your theme.
When you adopt transforms:
- Prefer one pipeline. Either WordPress intermediates for known crops, or CDN transforms for fluid sizes - not both fighting over the same markup.
- Keep format negotiation at the edge (AVIF/WebP with JPEG fallback) so editors can still upload JPEG from cameras and phones.
- Invalidate or version when you replace a master file so long-lived edge caches do not serve yesterday’s crop.
WordPress already accepts WebP (since 5.8) and AVIF (since 6.5) as upload formats. You do not need TimThumb-era optimizer plugins solely to invent a resize endpoint. Use core for library truth; use the CDN when you need elastic dimensions.
Measure after the switch. Compare bytes for the LCP image and the median content image before and after. If edge transforms add latency on first hit, warm the common widths or keep WordPress intermediates for the above-the-fold set and reserve the CDN for long-tail sizes. The goal is predictable delivery, not a clever URL grammar.
Removing TimThumb from an inherited site
On any theme or plugin dump you did not author:
- Search
wp-contentfortimthumb.php,thumb.php, and strings liketimthumb.php?src=. - Delete the script after templates no longer call it.
- Replace markup:
// Old - do not ship.
echo '<img src="' . esc_url( get_template_directory_uri() . '/timthumb.php?src=' . $url ) . '" alt="" />';
// New - Media Library path.
the_post_thumbnail( 'blog-list' );- Regenerate intermediates, purge CDN cache for old TimThumb URLs, and re-check the homepage LCP image.
Need a second pair of eyes on an old theme still calling resize scripts? Talk to a WordPress developer who can strip TimThumb, register the sizes your layouts need, and wire CDN transforms without leaving a PHP endpoint on the origin.
Practical checklist for 2026
- No
timthumb.php/thumb.phpin themes or mu-plugins. - Featured images and content images go through attachment APIs.
- Size names match real templates; unused sizes removed.
srcsetpresent on content images;sizescorrected for tight layouts.- On-the-fly needs live on the CDN, not in theme PHP.
- WebP/AVIF in the delivery path where the stack supports them.
TimThumb taught the ecosystem that “convenient resize in the theme” is a security and ops debt. Core intermediate sizes, srcset, and edge transforms are the maintained stack. Use them and leave the 2011 script in the history books.





