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 seeWhat it usually means
Broken image iconThe image request returned 404 Not Found or 403 Forbidden
Blank white spaceA lazy-loading script, CSS rule, or missing image source prevented the image from rendering
Alt text onlyThe 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:

  1. DevTools > Network > Img filter. Reload. Read the status column.
  2. Right-click the broken image, copy image address, open in a new tab.
  3. Compare that URL against the real file path over SFTP or File Manager.
  4. 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.

CodeMeaningWhere to look first
404The requested file or URL path was not foundUploads folder, image URL, siteurl / home values
403The resource exists or is protected, but access is deniedFile permissions, hotlink protection, WAF/security rules
500The server encountered an error while processing the request.htaccess syntax, PHP/server error logs
200The server successfully delivered a response, but the browser still doesn’t render the imageFile 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.

TargetRecommended valueNotes
Directories755 or 750Applies to uploads and its year/month subfolders
Files644 or 640Applies to images, .htaccess, theme files, and other regular files
wp-config.php440 or 400Use tighter permissions because it contains database credentials
Any directoryNever 777Avoid 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="http to 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 nameDimensionsCreated when
thumbnail150 × 150, typically croppedWhen WordPress generates thumbnail sizes
medium300 × 300 maximum, proportionalOriginal is larger than the requested size
medium_large768px maximum width, proportional heightOriginal is wider than 768px
large1024 × 1024 maximum, proportionalOriginal 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.

LayerExamplesPurge order
Server / object cacheVarnish, LiteSpeed Cache, RedisFirst
Plugin / page cacheWP Rocket, W3 Total CacheSecond
CDN edge cacheCloudflare, BunnyCDNThird
Browser cacheChrome, FirefoxLast

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:

  1. Deactivate all plugins
  2. Switch to Twenty Twenty-Four
  3. Reload the broken page
  4. 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.

FormatCore support sinceServer requirement
WebPWordPress 5.8 (July 2021)GD or Imagick with WebP/libwebp support
AVIFWordPress 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://yourdomain to https://yourdomain makes the change permanent
  • upgrade-insecure-requests in 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.

SettingWorkable valueSymptom when too low
memory_limit256MLarge images fail during processing or thumbnails are missing
upload_max_filesize64MUpload is rejected because the file exceeds the limit
post_max_sizeHigher than upload_max_filesizeLarge POST/file uploads can fail or be rejected
max_execution_time300 secondsBulk 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.

SymptomLikely causeFirst move
Grid is blank, list view worksJavaScript error or failed media API requestOpen the browser console and Network tab
Spinner never stopsBlocked admin-ajax.php or REST API requestCheck the failed request and whitelist only if a security rule is blocking it
Thumbnails are grey after a restoreMissing or inconsistent attachment metadata/filesRun wp media regenerate and verify the original files exist
Some images are invisibleBrowser extension, ad blocker, or security filter is blocking specific URLsTest 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.