You publish a post, load the page, and every image is a grey box with a jagged corner. Nothing else changed.
WordPress images not showing almost always traces back to one of ten causes: file permissions, migration leftovers, a cached 404, missing thumbnail sizes, or a plugin rewriting the src attribute.
The fix depends entirely on the status code the browser gets back. A 403 and a 404 need opposite repairs.
This guide walks through the diagnosis first, then each cause in order of how often it turns up:
- Permissions, .htaccess rules, and hotlink protection
- Migration, cache, CDN, and mixed content
- PHP limits, thumbnails, and Media Library failures
What Does “WordPress Images Not Showing” Mean?
WordPress images not showing is a rendering failure where the <img> element loads in the page markup but the browser cannot fetch or paint the referenced file.
The post content is intact. The break sits in the file path, the server response, or the browser security layer.
HTTP Archive parsed more than 10 million pages for the 2024 Web Almanac and found 99.9% requested at least one image. A page with broken images is, for most visitors, a broken page.
Three symptoms show up, and they point in different directions.
| What you see | What it usually means |
|---|---|
| Broken image icon | The image request returned 404 Not Found or 403 Forbidden |
| Blank white space | A lazy-loading script, CSS rule, or missing image source prevented the image from rendering |
| Alt text only | The HTML markup exists, but the image file is missing, inaccessible, or failed to load |
The status code matters more than the icon.
Have you seen the latest WordPress statistics?
Discover the latest WordPress statistics: market share, security trends, performance data, and revenue insights that shape the web.
Check Them Out →404: the path is wrong or the file was never written to disk.
403: the file exists, and something refuses to serve it (permissions, hotlink rules, a firewall).
200 with nothing rendered: the file downloads but the browser cannot decode it. Corrupt upload, wrong MIME type, or a format the browser rejects.
Every uploaded file lives under /wp-content/uploads/YYYY/MM/ unless you changed the default in Settings > Media.
Three failure zones exist and they rarely share a cause. Breakage on the public-facing side of the site is usually a path, permission, or cache problem.
Breakage inside the Media Library grid is almost always JavaScript or a blocked admin-ajax.php request. Breakage in the block editor alone points at script concatenation or a browser extension.
Worth separating early: a site where images refuse to display anywhere is a server-level issue, while a single stubborn thumbnail is a file-level one.
What Causes Images to Stop Displaying in WordPress?
Ten causes cover nearly every case: wrong file permissions, incorrect siteurl and home values after migration, broken .htaccess rules, missing thumbnail files, stale cache and CDN copies, plugin conflicts, mixed content, PHP memory limits, hotlink protection, and corrupted attachment metadata.
Ranked roughly by how often they turn up in WordPress.org support threads:
- Migration leftovers. Old domain hardcoded in post content and options.
- File permissions and ownership. The web server user cannot read the uploads folder.
- Cache and CDN staleness. A cached 404 outliving the fix.
- Missing generated sizes. The full image exists, the 300×300 crop does not.
- Plugin conflicts. Two lazy loaders, or an optimizer that deleted originals.
- .htaccess and hotlink rules. Server config saying no to its own site.
- Mixed content. HTTP image URLs on an HTTPS page.
- PHP memory and image libraries. Resize jobs dying mid-run.
- Attachment metadata corruption. The database records sizes that never existed.
- Unsupported file types. HEIC, and SVG, which WordPress blocks by default.
One question splits the whole list in half. Copy the image address, paste it into a new tab, and hit enter.
Image loads in the tab: the file is fine. Your problem is theme code, a plugin, or the markup.
Image does not load: the problem is server-side. Path, permissions, or a block.
That single test saves an hour. I run it before touching anything else.
One format note, since it trips people up regularly. Vector files in SVG format upload cleanly only after you allow the MIME type, and the 2024 Web Almanac put SVG at 6.4% of all images on the web, so it comes up more than you would think.
How Do You Diagnose a Broken Image Before Changing Anything?
Open DevTools, filter the Network tab by Img, and read the status column. That one column tells you whether the file is missing, blocked, or broken. Everything else follows from the code you see there.
The sequence I use, in order:
- DevTools > Network > Img filter. Reload. Read the status column.
- Right-click the broken image, copy image address, open in a new tab.
- Compare that URL against the real file path over SFTP or File Manager.
- Look inside
/wp-content/uploads/2026/07/for the exact filename, dimension suffix included.
The console is the second source. A failed to load resource message names the exact URL the browser tried and the code it got back.
For anything that smells like a PHP problem during upload or resize, switch on debugging in wp-config.php:
define('WPDEBUG', true); define('WPDEBUGLOG', true); define('WPDEBUGDISPLAY', false);
That writes to /wp-content/debug.log. Reading the WordPress error log after a failed upload usually names the plugin or the memory ceiling responsible.
Reading HTTP Status Codes for Images
The code narrows the cause faster than any plugin.
| Code | Meaning | Where to look first |
|---|---|---|
| 404 | The requested file or URL path was not found | Uploads folder, image URL, siteurl / home values |
| 403 | The resource exists or is protected, but access is denied | File permissions, hotlink protection, WAF/security rules |
| 500 | The server encountered an error while processing the request | .htaccess syntax, PHP/server error logs |
| 200 | The server successfully delivered a response, but the browser still doesn’t render the image | File corruption, incorrect MIME type, invalid image data, or browser/decode issues |
A repeated 404 across a site points at paths. A repeated 403 on image files only points at rules.
How Do File Permissions Break WordPress Image Display?
Wrong permissions return 403 on files that exist. The web server process cannot read the uploads directory or traverse into it, so it refuses the request instead of serving the JPG sitting right there.
WordPress.org’s Advanced Administration Handbook sets the baseline.
| Target | Recommended value | Notes |
|---|---|---|
| Directories | 755 or 750 | Applies to uploads and its year/month subfolders |
| Files | 644 or 640 | Applies to images, .htaccess, theme files, and other regular files |
wp-config.php | 440 or 400 | Use tighter permissions because it contains database credentials |
| Any directory | Never 777 | Avoid world-writable directories, including upload folders |
Over SSH, two commands fix an entire tree:
find /path/to/wp-content/uploads -type d -exec chmod 755 {} ;
find /path/to/wp-content/uploads -type f -exec chmod 644 {} ;
In FileZilla, right-click the uploads folder, choose File permissions, tick Recurse into subdirectories, and apply 755 to directories only. Repeat with 644 for files only.
Ownership beats permissions. If chmod changes nothing, the files belong to the wrong user. The web server runs as www-data, apache, or your cPanel account user, and chown -R user:group is the actual fix.
Full walkthrough here: correcting permissions across a WordPress install.
Skip 777. It hands write access to every process on the box, and plenty of hosts strip it back automatically within the hour.
How Do Migration and Wrong Site URLs Break Image Paths?
Migration breaks images because WordPress stores absolute URLs. Move from staging.example.com to example.com and every stored image path still points at the old host, producing a site-wide 404 on media while pages themselves load normally.
Two values in wpoptions control the base: siteurl and home. Fix those first, then fix the content.
WP-CLI handles the content pass safely because it unserializes arrays before replacing:
wp search-replace 'https://staging.example.com' 'https://example.com' --skip-columns=guid --dry-run
Drop --dry-run once the counts look right. No SSH access? Better Search Replace does the same job from the dashboard.
Raw SQL find-and-replace through phpMyAdmin is where sites get wrecked. Serialized arrays carry byte-length prefixes, and a replacement that changes string length corrupts widget settings, theme mods, and plugin options at once. That is how a broken image turns into a database error on the whole site.
Fixing Image URLs Left Behind After a Staging Push
Push direction matters. Copying a staging database over production carries staging URLs with it, and media breaks the moment the push finishes.
Two checks before you call it done:
- Search post content for
src="httpto catch hardcoded absolute paths - Check page builder tables separately, since Elementor and Divi store URLs in their own serialized blobs
Duplicator, All-in-One WP Migration, and WP Migrate all run the replacement during transfer instead of after.
How Do .htaccess Rules and Hotlink Protection Block Image Files?
Server config returns 403 on files that exist and are readable. Rewrite rules, hotlink protection, and security plugin directives all sit above WordPress in the request chain, and each can refuse an image request before PHP ever runs.
Corrupted rewrite block: a mangled WordPress block in .htaccess catches static files it was never meant to touch.
Hotlink protection with the wrong referrer: the rule lists example.com but the site serves from www.example.com, so your own pages get blocked.
Security plugin directives: Wordfence and iThemes Security both write rules that can lock down wp-content harder than intended.
Cloudflare produces a well documented version of this. Beaver Builder’s knowledge base records Hotlink Protection under Scrape Shield throwing 403 on CSS background images when a separate image CDN is in play, and Cloudflare’s own docs offer a hotlink-ok directory as the carve-out.
To regenerate a clean rewrite block, open Settings > Permalinks and click Save Changes without altering anything. WordPress rewrites the file. If your post URLs died at the same moment your images did, you are looking at a permalink structure that stopped resolving rather than an image problem.
On Nginx there is no .htaccess. The equivalent lives in the server block:
location ~* .(jpg|jpeg|png|gif|webp|svg)$ { validreferers none blocked example.com www.example.com; if ($invalidreferer) { return 403; } }
Miss a hostname in validreferers and you block yourself.
Why Do Thumbnails and Featured Images Show as Broken?
Full-size images load while thumbnails break because WordPress generates cropped copies as separate files on upload. If those copies were never written, or were left behind during a migration, the database still references sizes that no longer exist on disk.
| Size name | Dimensions | Created when |
|---|---|---|
| thumbnail | 150 × 150, typically cropped | When WordPress generates thumbnail sizes |
| medium | 300 × 300 maximum, proportional | Original is larger than the requested size |
| medium_large | 768px maximum width, proportional height | Original is wider than 768px |
| large | 1024 × 1024 maximum, proportional | Original is larger than the requested size |
Beyond those, WordPress writes 1536 and 2048 variants for retina, and anything over 2560px gets a -scaled copy that becomes the real serving file.
Add a theme size after uploads happened and nothing regenerates. A new addimagesize() call applies only to future uploads, which is why a theme switch strands featured images across an entire archive.
The repair is one command:
wp media regenerate --yes
Regenerate Thumbnails does the same from the dashboard for sites without WP-CLI.
Featured images break on their own path because thepostthumbnail() defaults to the 150px square when no size argument is passed. Missing that one file kills every archive card while in-content images stay fine.
Resource limits cause the original gap. OpenStreetMap’s server configuration commit from March 2023 raised WPMEMORYLIMIT to 256M specifically to let large image resizes finish, which is the same wall smaller sites hit silently during bulk uploads.
If the upload itself throws an error rather than just skipping sizes, that is a different failure. Start with uploads being rejected outright before regenerating anything.
How Do Caching and CDN Layers Serve Broken Images?
Caching layers store the failure alongside the file. Fix an image on the server and the edge keeps returning the old 404 for hours, because a negative response was cached before anyone noticed the problem.
W3Techs data from June 2026 puts Cloudflare in front of 23.4% of all websites and 83.5% of sites running any known reverse proxy. Odds are decent something sits between your server and the visitor.
Purge in order. Going straight to the CDN while a server cache still holds the bad response wastes the trip.
| Layer | Examples | Purge order |
|---|---|---|
| Server / object cache | Varnish, LiteSpeed Cache, Redis | First |
| Plugin / page cache | WP Rocket, W3 Total Cache | Second |
| CDN edge cache | Cloudflare, BunnyCDN | Third |
| Browser cache | Chrome, Firefox | Last |
LiteSpeed Cache carries 7 million active installs on WordPress.org and W3 Total Cache around 900,000, so the plugin layer is nearly always in play.
Cloudflare adds two features that touch image bytes directly: Polish recompresses them, Mirage swaps them for lightweight placeholders on slow connections. Both are worth switching off during a diagnosis.
Jetpack’s Image CDN rewrites every image URL to i0.wp.com and its siblings. Three conditions break it: the original file must stay on your server, hotlink protection must be off, and the Photon/1.0 user agent must not be blocked by a security plugin or firewall rule.
Automattic’s own issue tracker documents the staging case. A site cloned to a WP Engine staging URL with Site Accelerator still enabled returned 403 Forbidden on every request to i0, i1, and i2.
Test in an incognito window before blaming the server. Half the “still broken” reports are a browser holding a cached copy of the broken state.
Which Plugins and Themes Break Image Rendering?
Plugins break images by rewriting markup after WordPress builds it. Lazy loaders, optimizers, and format converters all filter the src attribute, and two of them running at once produce output no browser can resolve.
Double lazy loading: WordPress has applied loading="lazy" natively since version 5.5, so a lazy load plugin on top of it strips the real src and hands over a placeholder.
Optimizer overwrites: Smush (1 million+ installs), EWWW (around 1 million), ShortPixel (300,000+) and Imagify all rewrite files during bulk runs, and an interrupted run leaves half a library pointing at files that no longer exist.
Theme size registration: a theme switch changes which sizes templates request without generating them.
Native lazy loading now covers 29% of websites, and roughly 84% of that adoption traces back to WordPress core defaults rather than a deliberate choice, per theStacc’s 2026 analysis.
Google’s Martin Splitt confirmed in August 2025 that JavaScript lazy loaders using data-src instead of src create an indexing risk, since Google will not index images without a real src attribute.
The isolation test, in order:
- Deactivate all plugins
- Switch to Twenty Twenty-Four
- Reload the broken page
- Reactivate one plugin at a time, testing after each
Health Check & Troubleshooting runs that whole sequence for your session only, so visitors never see a plugin-free site. If the images return the moment you switch themes, you are looking at a theme-level failure rather than a plugin one.
Breakage that starts the same day as a bulk update points somewhere else. Update failures that leave plugins half-installed take image filters down with them.
WebP and AVIF Delivery Failures
Format support lives on the server, not in WordPress.
| Format | Core support since | Server requirement |
|---|---|---|
| WebP | WordPress 5.8 (July 2021) | GD or Imagick with WebP/libwebp support |
| AVIF | WordPress 6.5 (March 2024) | PHP 8.1+ with GD or Imagick 7.0.25+ with AVIF/libavif support |
WebP reaches roughly 96% browser coverage and AVIF around 93%, so the gap is rarely the visitor’s browser. It is a shared host missing the library, which SiteGround flags as a common shortfall.
How Do Mixed Content and HTTPS Errors Hide Images?
Mixed content failures come from the browser, not the server. An HTTP image URL sitting on an HTTPS page gets upgraded or refused before the request reaches your host, so the file is fine and the page still shows nothing.
MDN documents the current behavior: browsers auto-upgrade image, video, and audio requests from HTTP to HTTPS, and block insecure requests for every other resource type outright.
When the upgrade fails (expired certificate, wrong hostname on the cert, no HTTPS on the source), the image dies quietly with a console warning instead of a 404.
Chrome started blocking mixed content by default in version 79. Google has since announced that Chrome 154 will turn on “Always Use Secure Connections” by default in October 2026, with Enhanced Safe Browsing users getting it from Chrome 147 in April 2026.
HTTPS adoption plateaued at 95 to 99% years ago according to Google’s own transparency data, which is why the remaining insecure asset references stand out so sharply now.
Three fixes, in order of how invasive they are:
- Really Simple SSL, at over 5 million active installs, rewrites insecure URLs on the fly
- A database search-replace of
http://yourdomaintohttps://yourdomainmakes the change permanent upgrade-insecure-requestsin your Content Security Policy handles third-party stragglers
Behind a load balancer or reverse proxy, WordPress often cannot tell it is on HTTPS at all. Setting FORCESSLADMIN and reading HTTPXFORWARDEDPROTO in wp-config.php fixes the detection.
Console warnings about scripts loading from unauthenticated sources usually appear in the same batch as the missing images. A valid certificate does not stop individual assets from failing, and broader certificate and SSL configuration faults produce the same symptom on every file at once.
How Do PHP Limits and Image Libraries Stop Images From Generating?
PHP limits kill image generation midway. The upload finishes, the original lands in the uploads folder, and the resize job dies before writing thumbnails, leaving a full-size file with no smaller versions and a metadata record that lies about it.
| Setting | Workable value | Symptom when too low |
|---|---|---|
memory_limit | 256M | Large images fail during processing or thumbnails are missing |
upload_max_filesize | 64M | Upload is rejected because the file exceeds the limit |
post_max_size | Higher than upload_max_filesize | Large POST/file uploads can fail or be rejected |
max_execution_time | 300 seconds | Bulk image regeneration or processing stops partway through |
WordPress ships two separate memory constants, hardcoded in wp-includes/default-constants.php. WPMEMORYLIMIT defaults to 40MB for the front end and WPMAXMEMORYLIMIT to 256MB for admin work, which is where resizing happens.
Neither can exceed the server’s own memorylimit. That ceiling is set in php.ini, and knowing where that file lives on your host saves a support ticket.
Check the library situation at Tools > Site Health > Info > Media Handling. It names the active editor (Imagick or GD) and lists which formats it can write.
WordPress.org’s statistics API for 2026 puts PHP 8.2 as the largest branch at 25.8% of reporting sites, with PHP 7.4 still holding 19.3%. AVIF generation needs PHP 8.1 or newer, so a fifth of the ecosystem cannot produce it at all.
HEIC files from iPhones upload without complaint and render nowhere, since no major browser decodes them.
In the debug log, the line to look for reads Allowed memory size of X bytes exhausted during wpgenerateattachmentmetadata. That single entry confirms a memory ceiling rather than a file problem.
Uploads that fail at the server boundary throw a different error entirely. A 413 response on a large file means the request never reached PHP. Turn on visible PHP error reporting only on staging, never on a live site.
How Do You Fix Images Not Showing Inside the Media Library and Editor?
Admin-side breakage has different causes than front-end breakage. The Media Library grid runs on JavaScript and REST requests, so a blocked endpoint or a script error empties the grid while the files themselves sit untouched on disk.
| Symptom | Likely cause | First move |
|---|---|---|
| Grid is blank, list view works | JavaScript error or failed media API request | Open the browser console and Network tab |
| Spinner never stops | Blocked admin-ajax.php or REST API request | Check the failed request and whitelist only if a security rule is blocking it |
| Thumbnails are grey after a restore | Missing or inconsistent attachment metadata/files | Run wp media regenerate and verify the original files exist |
| Some images are invisible | Browser extension, ad blocker, or security filter is blocking specific URLs | Test in a clean browser profile/incognito window |
Security plugins block admin-ajax.php more often than people expect, and every asynchronous request the dashboard makes runs through it.
Script errors cascade. A missing jQuery reference in the admin takes out the media modal, the uploader, and the featured image panel in one go.
When admin scripts get bundled and something in the bundle fails, unbundle them:
define('CONCATENATESCRIPTS', false);
WordPress.com documents a case worth knowing. On a site set to private, turning on the site accelerator’s image speed-up setting makes images return 403 Forbidden inside the Media Library itself, because the CDN can only fetch publicly reachable files.
Ad blockers cause the strangest version of this. Any file named something like banner-ad-300x250.jpg gets hidden by filter lists, and the file loads perfectly with the extension off.
Persistent grid failures that survive all of the above usually trace to deeper media library corruption rather than a front-end script.
How Do You Keep WordPress Images From Breaking Again?
Prevention comes down to four habits: migrate with tools that handle serialized data, run one plugin per job, verify permissions after every restore, and monitor image assets rather than just page URLs.
Migration first. Duplicator, All-in-One WP Migration, and WP Migrate all rewrite URLs during transfer, which is why they succeed where a raw SQL dump plus a manual find-and-replace usually does not.
Importing content from another install has its own failure mode. Media that silently fails to import leaves posts referencing a domain you no longer control.
One plugin per job. The WordPress.org directory carries over 2,400 image optimization plugins, and running two of them means two sets of filters fighting over the same src attribute.
The same rule applies to caching. Two caching plugins active at once serve each other’s stale files, which produces exactly the display errors you spent last weekend fixing.
A short post-migration checklist covers the rest:
- Reset directory and file permissions after every host move or restore
- Reload one image-heavy page in an incognito window before calling the migration done
- Confirm the CDN is pointed at the new origin, not the old one
Monitoring is the part most people skip. Uptime tools ping the homepage and report green while every image on the site returns 404, so pick a crawler that checks assets and set it to flag broken references across the site on a schedule.
Last habit, and the cheapest one: push plugin and theme updates to staging first, load a gallery page, and look at it. Thirty seconds of looking beats an afternoon of log reading.
FAQ on WordPress Images Not Showing
Why did my images disappear after moving my site?
Migration leaves the old domain hardcoded in post content and in the siteurl and home values. Run a search-replace that handles serialized data, using WP-CLI or Better Search Replace, never a raw SQL edit.
What permissions should the uploads folder have?
Directories take 755, files take 644. Apply both recursively to /wp-content/uploads/. If nothing changes, the files belong to the wrong user, so fix ownership with chown before touching permissions again.
Why do full-size images work but thumbnails break?
WordPress writes separate files for each registered size on upload. Missing crops mean those files were never generated or were lost in transfer. Run wp media regenerate or install Regenerate Thumbnails.
My image loads in a new tab but not on the page. Why?
The file is fine, so the problem sits in your theme or a plugin. Lazy loading conflicts cause most of these, especially when a plugin duplicates the native loading attribute WordPress already adds.
How do I tell a 403 from a 404?
Open DevTools, filter the Network tab by Img, and read the status column. A 404 means the path is wrong. A 403 means the file exists and something refuses to serve it.
Can Cloudflare cause broken images?
Yes. Cloudflare caches the 404 response and keeps serving it after you fix the file. Hotlink protection also blocks your own images when the referrer list misses a hostname. Purge, then retest.
Why does the Media Library show grey boxes?
Grid view runs on JavaScript and REST requests. A blocked admin-ajax.php endpoint or a script error empties it while the files stay intact. Check the browser console first.
What memory limit do image resizes need?
Set 256M. WordPress defaults to 40MB on the front end and 256MB for admin tasks, but the server ceiling in php.ini overrides both. Large uploads fail silently below that.
Do I need to clear the cache in a specific order?
Yes. Server cache first (Varnish, LiteSpeed, Redis), then the caching plugin, then the CDN, then your browser. Skipping ahead means the stale response gets re-cached immediately.
Why are my images blocked on an HTTPS page?
HTTP image URLs on a secure page trigger mixed content handling. Browsers try upgrading the request, and blocked ones die with a console warning rather than a 404. Replace the URLs permanently.
Conclusion
WordPress images not showing looks like a design problem and behaves like a server one. The tag is almost never at fault.
Work the sequence rather than guessing. Read the status code, test the direct image URL, then repair the layer that code points at.
Most sessions end in one of four places:
- Ownership and permissions on the uploads directory
- Stale URLs in wp_options` after a host move
- Missing generated sizes, fixed by wp media regenerate
- An optimizer or lazy loader rewriting output
Check Site Health > Info > Media Handling before you touch anything else. If GD or ImageMagick is missing WebP support, no amount of plugin swapping helps.
Then fix it once, on staging, and push it live.


