Open a page on your phone, zoom in on the hero photo, and you’ll usually spot the problem inside two seconds: soft edges, muddy detail, a faint smear where there should be a crisp line. Blurry images are one of those defects that visitors notice before they can explain why. Nobody consciously thinks “the pixel density mismatch on this JPEG is 0.6x”, they just think the site looks cheap. Fixing it means understanding what actually happens to a photo between the moment you drag it into the WordPress Media Library and the moment it renders on someone’s 3x-density iPhone screen, because there are at least six different places in that pipeline where quality gets lost.
The pipeline, step by step
When you upload an image, WordPress doesn’t store just the file you gave it. It runs the original through wp_generate_attachment_metadata(), which calls the image editor (GD or Imagick, depending on your server) to produce a set of additional sizes: thumbnail, medium, medium_large, large, and whatever custom sizes your theme registered with add_image_size(). Each of those is a fresh resample of the original, not a resize of a resize, so the quality of the original file matters more than people assume. If you upload a 900×600 JPEG that’s already been compressed twice by your phone and then by a screenshot tool, every derivative WordPress generates inherits that loss.
Then the theme picks which size to actually output, using the srcset and sizes attributes that wp_calculate_image_srcset() builds automatically since WordPress 4.4. The browser is supposed to look at the container width and the device’s pixel ratio, then choose the best candidate from the srcset list. This is where a lot of blur gets introduced silently: if your theme’s CSS renders an image at 800px wide but the largest srcset candidate available is 640px, the browser has no choice but to scale up a smaller source, and scaling up always softens detail. This mismatch is one of the most common root causes of “blurry” complaints and it has nothing to do with the original photo’s quality at all.
Resolution and pixel density aren’t the same problem
Uploading a “big enough” image and uploading a “high resolution enough” image are two different requirements, and conflating them is where most troubleshooting goes sideways. A standard laptop display might be 1x pixel density, one CSS pixel equals one physical pixel. A MacBook Retina display or a modern phone is commonly 2x or 3x. That means if your image is displayed at 400 CSS pixels wide, a 1x screen needs a 400px-wide file to look sharp, but a 3x screen needs 1200px to look equally sharp on that same visual footprint. If WordPress’s srcset only offers up to 800px because that’s the largest registered image size, the 3x screen is stuck upscaling an 800px file to fill a visual space that wants 1200px of real data. The image isn’t wrong, it’s simply outgunned by the display it’s rendering on.
You can check this yourself without any plugin. Right-click the blurry image in Chrome, choose Inspect, and look at the “Computed” tab for the actual rendered width and height in CSS pixels. Then open the Network tab, reload, and find which file in the srcset the browser actually requested, the URL will usually have a -768x432 or similar suffix. Multiply the rendered CSS width by your screen’s device pixel ratio (visible in the console by typing window.devicePixelRatio) and compare that number to the actual pixel width of the file that loaded. If the file is smaller than that target number, you’ve found your answer, and it’s a straightforward one: the source file or the registered image size needs to go up, not the compression setting.
Compression: where WordPress trades size for speed
WordPress applies JPEG compression at 82% quality by default via the wp_editor_set_quality filter’s default value, a number chosen years ago as a reasonable middle ground between file size and visual fidelity. Under most conditions 82% is genuinely hard to distinguish from 100% on a photo with natural texture, skin, foliage, fabric. It becomes visible fast on images with flat color fields and hard edges: screenshots, illustrations, text overlays, logos. JPEG’s compression algorithm works by dividing the image into 8×8 pixel blocks and discarding high-frequency detail that the human eye is statistically less sensitive to; that math holds up well for photographs and falls apart on graphics with sharp geometric edges, which is exactly why a screenshot re-saved as JPEG tends to develop a visible halo or “ringing” around text.
Beyond WordPress core’s own quality setting, a second layer of compression often gets applied by an image optimization plugin, ShortPixel, Imagify, EWWW, Smush, and these plugins frequently default to “aggressive” or “smart” compression profiles that push quality lower than core’s baseline in exchange for smaller files. That’s a reasonable trade for a photo blog chasing Core Web Vitals scores, but it stacks with whatever compression the original camera or export tool already applied, and stacked lossy compression compounds artifacts rather than averaging them out. If a plugin was installed after the blur started, check its settings for a quality slider and nudge it up before assuming the problem is somewhere else.
Lazy loading and the placeholder illusion
Since WordPress 5.5, native lazy loading adds loading="lazy" to images below the fold automatically. That’s unrelated to blur in the end state, but a fair number of lazy-load plugins (and some CDN configurations) use a low-resolution placeholder or blurred SVG that swaps for the full image once it scrolls into view or once the connection allows. If you’re taking a screenshot for a bug report the instant the page loads, or testing on a throttled connection in DevTools, you might be capturing the placeholder mid-transition rather than the final asset. Test by waiting a full two seconds after scroll-into-view before judging sharpness, and disable any “LQIP” (low quality image placeholder) feature temporarily to rule this out as a factor.
CDN and image-transformation services can silently downgrade quality
If your host or a service like Cloudflare, Jetpack’s Photon, or a WooCommerce-adjacent CDN rewrites your image URLs to serve resized/optimized versions on the fly, that layer has its own quality and format decisions completely independent of what you set in WordPress. Photon, for example, defaults to serving WebP to supporting browsers at a quality level it picks itself, and it also respects query parameters like ?quality=60 if your theme or another plugin appends them. Open the actual image URL that loaded (from the Network tab) in a new tab and look at the query string; if you see unfamiliar parameters like strip=all, quality=, or fit=, you’re looking at a transformation layer doing work you didn’t explicitly configure, and that’s usually the fastest lead to chase.
CSS scaling and background-image misuse
Not every blur problem originates in the image pipeline. A common pattern in customized themes is setting a fixed CSS width larger than the image’s natural dimensions, using width: 100% on a container without an appropriately sized source, or using an image as a CSS background-image with background-size: cover on a container that’s simply bigger than the source file. Because CSS scaling upsamples using the browser’s own interpolation (bilinear by default, though you can force image-rendering: pixelated or other hints), an undersized source will always look soft no matter how good the original compression was. This is especially common after a theme redesign where hero banner containers got wider but nobody re-uploaded the banner image at the new dimensions.
The Big Image Threshold, and why huge uploads don’t always help
WordPress 5.3 introduced a “Big Image Threshold” that automatically scales down any upload wider than 2560px (filterable via big_image_size_threshold) before generating the standard size set. This exists to stop people from unknowingly serving 6000px camera originals to a browser that only needs 800px, which would be wasteful and slow. It means that uploading an enormous image doesn’t guarantee sharper output past 2560px, if your design genuinely needs a source larger than that (a full-bleed background image on an ultra-wide monitor, for instance), you’ll need to either raise that filter or work around it deliberately, otherwise WordPress will cap you at 2560px regardless of what you dragged in.
GD versus Imagick: the invisible library choice
Almost nobody picks which image library their server uses, it’s usually whatever the hosting company compiled into PHP, and it quietly shapes output quality in ways that never show up in any WordPress settings screen. GD is the older, more universally available library; it does resampling with a fairly basic algorithm and, in older PHP/GD combinations, has had known issues with JPEG chroma handling that produce slightly muddier output than Imagick on the same source file at the same quality setting. Imagick, built on ImageMagick, generally produces sharper resampling and better handles color profiles (ICC profiles embedded by cameras and editing software), but it’s heavier on server resources, which is exactly why budget shared hosts often disable it and fall back to GD. You can check which one your site is actually using under Tools > Site Health > Info > Media Handling in the WordPress admin. If you’re on GD and blur is a persistent, site-wide complaint rather than a one-off image problem, asking your host to enable the Imagick PHP extension is a legitimate, often-overlooked fix that costs nothing and requires no plugin.
Color profiles and the “flat” look that reads as blur
A related but distinct issue: images edited in Photoshop or Lightroom often carry an embedded Adobe RGB or ProPhoto RGB color profile. Most web browsers render in sRGB, and if the image editor doesn’t convert the profile on export, colors can shift and contrast can flatten in a way that visually reads as softness even though every pixel is technically sharp. This is a common trap for sites publishing product photography or portfolio work shot by a professional photographer using a wide-gamut workflow. The fix lives entirely outside WordPress: in the export dialog of whatever editing software produced the file, explicitly convert to sRGB before saving, rather than leaving the working color space embedded.
Mobile data-saver modes add a variable you can’t control
Chrome’s Lite mode and several Android manufacturer browsers include a data-saving feature that proxies images through Google’s servers and re-compresses them more aggressively on cellular connections, independent of anything your site does. A visitor complaining that “images look blurry on my phone but fine on my laptop” may simply have this setting enabled. It’s worth asking testers to check their browser’s data-saver setting before spending hours debugging a WordPress-side cause that doesn’t exist. This is also why bug reports about blur are notoriously inconsistent between testers, two people on the same page, same WiFi, different phone settings, can genuinely see two different images.
A real troubleshooting example
A membership site running BuddyPress once reported that avatar images looked “smeared” specifically in the activity feed but sharp on individual profile pages. The avatars were uploaded at a reasonable 300×300px, well above what either context needed. The actual cause turned out to be a theme template calling bp_activity_avatar() without a specified width, which fell back to BuddyPress’s tiny 20×20px default thumbnail size, then a mismatched CSS rule in a child theme stretched that same 20px thumbnail to 48px in the feed layout with plain CSS scaling. Nothing was wrong with the uploads, the compression, or the server, a single unscoped CSS selector was upscaling a deliberately small thumbnail by more than double. The fix was one line: passing an explicit, larger width/height argument to the template function so BuddyPress requested (or generated) an appropriately sized derivative instead of stretching the smallest one available. The lesson generalizes well beyond BuddyPress: when blur is isolated to one specific template or component rather than site-wide, the cause is almost always in that template’s markup or CSS, not in the Media Library.
A practical diagnostic sequence
Rather than guessing, work through this in order, because each step rules out a specific stage of the pipeline:
- Confirm the rendered CSS size of the image and your screen’s device pixel ratio using DevTools, and calculate the ideal source width as described above.
- Check the Network tab for the actual file that loaded and its real pixel dimensions (open the file directly and check its properties, or hover in DevTools).
- Compare that to the ideal width. If it’s smaller, the fix is a larger registered image size or a larger original upload, not a compression setting.
- If dimensions check out but the image still looks soft, inspect the URL for CDN transformation parameters and check any active image-optimization plugin’s quality slider.
- Rule out lazy-load placeholder artifacts by waiting after scroll and disabling LQIP temporarily.
- Check the theme’s CSS for the specific image class to confirm nothing is scaling it beyond its natural size.
- As a last resort, clear any page cache and CDN edge cache, since a genuinely fixed image can still appear blurry to you personally if you’re viewing a stale cached copy.
What to actually do once you’ve found the cause
If the source resolution is genuinely too small for the display context, re-export the original at a larger size or, better, use a vector format (SVG) for anything that isn’t a photograph, logos, icons, simple illustrations scale perfectly at any density because they’re described mathematically rather than as a pixel grid. For photographs, aim to upload originals at least 2x the largest width your theme will ever display them at, and let WordPress’s automatic srcset generation handle serving the right size to the right device; don’t try to manually pick “the” size, because there isn’t one right answer across a phone, a tablet, and a 4K monitor.
If compression is the culprit, most optimization plugins let you set a per-image or global quality floor. For photographic content, 75-85% JPEG quality is usually invisible to the eye while cutting file size substantially; for anything with hard edges or text, either bump quality closer to 90-95% or switch that specific asset to PNG, which is lossless and appropriate for graphics even though it produces larger files than JPEG for photographs. WebP and AVIF split the difference nicely for most modern sites, smaller than JPEG at equivalent visual quality, and WordPress has supported WebP uploads natively since 5.8, with AVIF support added in 6.5, so there’s rarely a reason to fight the older formats anymore unless you need broad legacy browser support.
If a CDN transformation layer is the source, most services expose their own quality and format settings in a dashboard separate from WordPress; Cloudflare’s Image Resizing and Polish features, for example, have their own quality slider that overrides whatever WordPress generated. Check there directly rather than assuming WordPress settings apply.
Finally, if you’re still unsure after working through the checklist, it’s worth testing with a known-good high-resolution PNG in an unstyled page (the default Twenty Twenty-Four theme, no custom CSS) to isolate whether the issue is systemic to your WordPress install or specific to your active theme and plugin stack. That single test collapses the search space dramatically and usually points straight at the actual offending layer.
Printing confusion: why “300 DPI” advice doesn’t apply here
Photographers and designers sometimes bring print habits into web work, insisting an image needs to be “300 DPI” to look sharp. DPI (dots per inch) is a print concept describing physical ink density on paper; it has no meaning on a screen, where what matters is the raw pixel count relative to the display area, not an arbitrary density label baked into the file’s metadata. A 300 DPI image that’s only 200 pixels wide will look exactly as blurry on a browser as a 72 DPI image at the same 200-pixel width, the DPI tag is simply ignored by every browser. If you’re pulling assets from a print designer’s file, ask for the pixel dimensions, not the DPI setting, and re-export at a pixel width appropriate for the largest CSS container it will ever fill, accounting for 2x or 3x pixel density as covered earlier.
Related reading:
Best Managed WordPress Hosting Providers