Troubleshooting

Allowed Memory Size Exhausted: Where the WordPress Memory Limit Lives

The "Allowed memory size exhausted" fatal error involves three separate ceilings, and the one most people raise is not the one that stopped the request. How PHP's memory_limit, WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT interact, and why images trigger the error more often than anything else.

Allowed Memory Size Exhausted: Where the WordPress Memory Limit Lives

The error arrives as a single line that says less than it appears to: Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes), followed by a file path and a line number. The path is usually somewhere in wp-includes or a plugin folder, and it moves depending on which upload or which screen tripped it.

Both numbers are worth reading. 134217728 bytes is 128M, the ceiling PHP was enforcing at that moment. 20480 bytes is 20KB, the allocation that got refused. The 20KB is not the culprit. Something before it had already consumed 128 megabytes, and the next small allocation happened to be the one that met the wall.

The standard response is to open wp-config.php, add define( 'WP_MEMORY_LIMIT', '512M' );, reload, and get the identical error with the identical 134217728 in it. That happens because WordPress has three separate memory numbers, they apply in different situations, and the one most people edit sits underneath the one that actually stopped the request.

Comparison of PHP's memory_limit, WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT, showing that raising the WordPress constants above the server limit changes nothing

Three ceilings, not one

The first ceiling is PHP’s own memory_limit, set in php.ini, a PHP-FPM pool config, or a host control panel. This is the number quoted in the fatal error, and it is the only one PHP enforces. Everything WordPress does on top of it is a request to change it.

WP_MEMORY_LIMIT is WordPress’s baseline request, and it applies to every request including front-end page loads. Core defaults it to 40M on a single site and 64M on multisite. During bootstrap it compares that value against the current PHP limit and calls ini_set( 'memory_limit', WP_MEMORY_LIMIT ) only when the constant is higher. It never lowers the limit, so a server already running at 256M is not dragged down to 40M.

WP_MAX_MEMORY_LIMIT is the higher ceiling WordPress asks for when it knows the work is heavy. It defaults to 256M, or to the php.ini value when that is already larger. It is not applied on every request; it is applied when something calls wp_raise_memory_limit(). wp-admin/admin.php calls it with the admin context on every admin screen, and both image editor classes call it with the image context when they load a file for resizing. Each context has its own filter, admin_memory_limit and image_memory_limit, so a plugin can adjust one without touching the constants. WP-Cron got its own cron_memory_limit context in WordPress 6.3.

The word doing the work in all of that is request. Both constants act through ini_set(). On many hosts the call succeeds, because memory_limit is one of the settings PHP allows a script to change at runtime. On plenty of others it does nothing, because the host has locked the value, disabled ini_set, or enforces the real ceiling outside PHP entirely: a container limit, a cgroup, an FPM pool cap. Core checks with wp_is_ini_value_changeable( 'memory_limit' ), and when the answer is no it stops guessing and defines both constants as the current PHP value.

That is the entire explanation for “I raised the limit and nothing changed”. Defining WP_MEMORY_LIMIT as 512M on a server that caps you at 128M and refuses runtime changes gives you a constant holding the string 512M and a PHP process that still dies at 134217728 bytes. The constant is not lying. It is a request nobody granted.

Seeing all three at once

Before editing anything, read the numbers. Tools → Site Health → Info has a Server section listing the PHP memory limit, and a WordPress Constants section listing WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT. One detail there is easy to walk past: when the limit in force on admin screens differs from the server’s, core adds a second row labelled “PHP memory limit (only for admin screens)”. That row appearing is proof the ini_set() call worked. Its absence means the two values are the same, which is either a generous server or a locked one.

For a live reading inside a real request, this notice prints all three plus what the request has used so far. It belongs in a small site-specific plugin or in your theme’s functions.php file, and it should come back out once you have the answer.

add_action( 'admin_notices', function () {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    echo '<div class="notice notice-info"><p>'
        . 'PHP memory_limit: ' . esc_html( ini_get( 'memory_limit' ) )
        . ' | WP_MEMORY_LIMIT: ' . esc_html( WP_MEMORY_LIMIT )
        . ' | WP_MAX_MEMORY_LIMIT: ' . esc_html( WP_MAX_MEMORY_LIMIT )
        . ' | peak so far: ' . esc_html( number_format( memory_get_peak_usage( true ) / 1048576, 1 ) ) . ' MB'
        . '</p></div>';
} );

Two things about that reading. memory_get_peak_usage( true ) with the true argument reports real memory allocated from the system rather than the smaller figure PHP tracks for its internal bookkeeping, and the real figure is the one memory_limit is measured against. And because the notice renders inside the admin, the memory_limit it prints is whatever is in force on admin screens, normally WP_MAX_MEMORY_LIMIT rather than the server baseline. Site Health is where the baseline is.

The notice also reads the peak at the moment it renders, which misses everything that happens later in the request. To catch the real peak, log it at shutdown instead, with both WP_DEBUG and WP_DEBUG_LOG set to true so the line lands in wp-content/debug.log. On a live site set WP_DEBUG_DISPLAY to false at the same time.

add_action( 'shutdown', function () {
    $peak = number_format( memory_get_peak_usage( true ) / 1048576, 1 );
    $uri  = isset( $_SERVER['REQUEST_URI'] )
        ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
        : 'cli';

    error_log( sprintf( 'peak %s MB of %s on %s', $peak, ini_get( 'memory_limit' ), $uri ) );
} );

Run an upload, a bulk regeneration, a slow admin screen, then read the log. It records the requests that finish, which is the point: ordinary front-end page loads sit well under the limit while one specific admin operation peaks several times higher, and that gap tells you whether you have a ceiling problem or a single routine doing too much.

If you have WP-CLI, the three numbers are one command:

wp eval 'echo ini_get("memory_limit") . " | " . WP_MEMORY_LIMIT . " | " . WP_MAX_MEMORY_LIMIT . PHP_EOL;'

Treat that reading with suspicion. WP-CLI runs under the CLI build of PHP, which frequently has a different memory_limit from the one serving web requests, sometimes none at all. A CLI reading of 512M is no evidence about what the browser-facing pool allows.

Why images trigger it first

A JPEG is compressed on disk and uncompressed in memory. The file size tells you almost nothing about what it costs to open.

Take a 4000 x 3000 photo, the ordinary output of a phone. That is 12 million pixels, and GD holds each pixel of a truecolor image as a 32-bit value, so the bitmap alone is roughly 48 million bytes, about 46MB, before any of PHP’s own overhead. On disk that same photo might be a 5MB JPEG. The ratio is close to ten to one, and it worsens with dimensions: 8000 x 6000 is four times the pixels and lands near 180MB as a bitmap.

Now add the resize. Producing a 300 x 300 thumbnail means the source bitmap and the destination bitmap are resident at the same time. The source is not released afterwards, because core builds every subsequent size from the same loaded original: make_subsize() resizes, saves, frees the copy, and returns to the original for the next one. The floor for handling that upload is therefore around 46MB held for the whole of metadata generation, no matter how small the outputs are.

Then multiply. Every additional crop a theme or plugin registers is another resize inside the same request, and a commercial theme plus a gallery plugin plus WooCommerce pushes past a dozen entries without trying. Checking how many image sizes your site actually registers is worth doing before blaming the limit, because bulk regeneration across all of them is the most memory-hungry thing a normal WordPress site ever does. A single upload survives it. Regenerating a few thousand attachments does not.

Two details make this less tidy than the arithmetic suggests. Since WordPress 5.3, core scales down originals whose longest side is over 2560 pixels and keeps a -scaled copy, but producing that scaled file still means loading the full original first, so the peak is unchanged. And if your server uses Imagick rather than GD, the pixel data lives in ImageMagick’s own cache, which PHP’s memory_limit does not count. Imagick sites tend to fail with a different message, or with no PHP message at all, while memory_limit looks perfectly healthy.

Diagram showing a 4000 by 3000 pixel photograph taking about 5 MB on disk but roughly 48 MB in memory as a bitmap, above WordPress's 40 MB default limit

The three ceilings are easier to reason about with the numbers filled in, and the error message itself carries two of them.

Paste the line from your log below. It converts the byte figures into megabytes, names the file and the plugin they came from, and then works out which of the three limits is actually in force in the front end, in the admin and during an upload, which is the part that surprises people. There is a calculator underneath for the other half of the question, how much a given image will need before it has even finished resizing.

Memory limit calculator

Paste the fatal error line from your debug.log and this reads the numbers out of it, then works out which of the three memory limits is really in force in the front end, in wp-admin and while uploading. Everything is worked out in this browser tab, nothing is sent anywhere.

1. Read the error line

Paste the whole line, or a few lines around it. The first line that mentions an exhausted memory size is the one that gets read.

Nothing read yet
Limit that ran outnot read yet
Tried to allocatenot read yet
Filenot read yet
Linenot read yet
Where that file livesnot read yet

A small figure under "tried to allocate" does not name the culprit. That allocation was only the last drop into a bucket that was already full, and PHP reports the drop, not the filling. What filled it is what you are looking for: an import, a query without a limit, an image being resized, a loop over thousands of posts.

2. The three limits, and which one applies where

There is not one memory limit, there are three, and they apply in different places.

SettingWhere it appliesDefault
memory_limit PHP itself, on every request. The hard ceiling: WordPress can ask for more, but only the host decides whether PHP is allowed to go there. set by the host, often 128M or 256M
WP_MEMORY_LIMIT The front end, on every normal page view. WordPress raises the limit to this value early in the request. 40M, 64M on a multisite
WP_MAX_MEMORY_LIMIT wp-admin, uploading and image editing, where the headroom is actually needed. 256M

WordPress only ever raises a limit, it never lowers one. A constant below the value already in force does nothing at all. A constant above what the host allows does nothing either: PHP stays where the host caps it.

Whole numbers with K, M or G, as PHP reads them: 256M. A plain byte count works too, and -1 in the first field means no limit. Leave the last field empty unless you know your host pins memory_limit down. Leave a WordPress field empty and its default is used.

ContextIn forceSet by
Front end, a normal page view128 MBmemory_limit
wp-admin, dashboard and editors256 MBWP_MAX_MEMORY_LIMIT
Uploading and image editing256 MBWP_MAX_MEMORY_LIMIT

    3. Where the memory goes

    Rough figures, switched on and off, so you can see which item dominates. Every number is named and can be changed.

    about 12 MB for a bare install 12 MB
    40 MB
    about 25 MB on top 25 MB
    about 30 MB on top 30 MB
    px, at width × height × 4 bytes × 2 91.6 MB
    at about 1 KB each 4.9 MB
    Estimated need168.6 MB
    In force in wp-admin256 MB

    That fits.

    These are estimates, not measurements. A plugin can sit at 0.5 MB or at 15 MB, and an import loop can eat more than everything else together. Use the figures to see which item dominates, then measure the real thing with Query Monitor or a profiler before you trust a number.

    4. The lines you need

    Exactly the lines, with the values from the fields above. Take the one that matches how your host runs PHP.

    php.ini, on a server you control
    memory_limit = 128M
    .user.ini, for PHP-FPM or CGI, next to wp-config.php
    memory_limit = 128M
    .htaccess, only where PHP runs as an Apache module
    php_value memory_limit 128M

    Under PHP-FPM, which is what most hosts run today, this line does nothing at best and answers every request with a 500 at worst. Use .user.ini there.

    wp-config.php, above the line that says stop editing
    define( 'WP_MEMORY_LIMIT', '40M' );
    define( 'WP_MAX_MEMORY_LIMIT', '256M' );
    The two constants only ever raise the limit, never lower it.

    Some hosts pin memory_limit down where no file of yours can reach it. Then none of the lines above changes anything, phpinfo keeps showing the old value, and only a ticket to the host helps. Ask for the value in megabytes and ask whether it applies to the web server and to WP-CLI alike.

    WunderPaint
    The Dynamic Design and Automation Studio
    WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

    The other usual suspects

    Images dominate, but three other shapes account for most of the rest.

    • Importing a large WXR file. The importer parses the export and holds post structures in memory before writing them, and a 60MB XML file does not become 60MB of PHP arrays, it becomes considerably more. Splitting the export into several smaller files works better than any constant.
    • A plugin loading an entire result set at once. 'posts_per_page' => -1 is harmless on a site with 200 posts and fatal on one with 40,000, because every row becomes a post object and core then primes the meta cache for all of them. The same shape turns up in order exports, CSV generators and rebuild-index routines.
    • Something serialising an enormous structure. Backup plugins building a full file manifest, page builders storing a whole layout tree in postmeta, analytics plugins writing large autoloaded options. Autoloaded options deserve their own look, since every one of them is read on every request, front end included, which usually shows up as a site that feels slow rather than one that is broken.

    Raising it properly, or not raising it

    The order matters more than the values.

    1. Measure first. Find the actual peak of the request that fails and compare it to the PHP limit. A request peaking just over 128M needs a different answer from one peaking at 190MB.
    2. Confirm the value can move at all, using the Site Health rows or the notice above. If nothing has been raised anywhere, no wp-config.php edit will change that, so skip to step four.
    3. Set the constants in wp-config.php.
    4. Ask the host. On managed WordPress hosting the pool limit is a support ticket rather than a file you own, and one message beats an afternoon of experiments.
    5. If the host will not move, reduce the work instead of raising the ceiling.

    The constants go anywhere above the line that begins /* That's all, stop editing in wp-config.php, because core reads them while loading and anything below that line arrives too late:

    define( 'WP_MEMORY_LIMIT', '256M' );
    define( 'WP_MAX_MEMORY_LIMIT', '512M' );

    On PHP-FPM and CGI setups you can often raise the underlying PHP value with a .user.ini file in the WordPress root, holding a single line. PHP caches those files for user_ini.cache_ttl seconds, 300 by default, so give it five minutes before deciding it failed:

    memory_limit = 256M

    You will also find advice to add php_value memory_limit to .htaccess. That directive only works when PHP runs as an Apache module, which is now the minority case, and on an FPM host it produces an immediate 500 error across the whole site. Check how your server runs PHP before trying it.

    Reducing the work is often the better engineering answer anyway. Regenerate thumbnails in batches rather than in one pass, or one size at a time. Resize enormous source files before uploading them: dropping a 6000 x 4000 original to 2560 pixels wide cuts the bitmap cost by more than half and costs nothing visually, since core was going to scale it anyway. In long-running loops, query with 'fields' => 'ids' and load objects one at a time. And keep in mind that a 1G limit on a small server is not free: each PHP worker can now claim a gigabyte, so fewer of them fit, and a machine that used to go slow under load starts refusing connections instead.

    When something looks wrong

    A blank page with no error text. A fatal error with display_errors off produces nothing at all in the browser. The message is still in the PHP error log or in wp-content/debug.log, and the white screen of death is worth reading up on separately, because half of debugging a memory error is getting the error to show itself in the first place.

    The byte count in the error never changes. Whatever you define, PHP keeps reporting the same allowed size. The ini_set() call is being refused, so the ceiling is set outside your reach. Confirm in Site Health and take it to the host.

    No PHP error at all, just a 502 or a truncated response. The process was killed from outside, by the OS out-of-memory killer, a container limit, or the FPM process manager. PHP never got the chance to report anything, so raising memory_limit makes this worse rather than better.

    The admin works but the front end fatals, or the reverse. That is the two constants doing exactly what they are designed to do. Admin screens run under WP_MAX_MEMORY_LIMIT, front-end requests under WP_MEMORY_LIMIT, and a plugin running heavy code in both places fails on the lower one first.

    Some large uploads work and others of similar file size do not. Memory cost tracks pixel count, not kilobytes. A heavily compressed 2MB JPEG at 6000 x 4000 costs far more to open than a lightly compressed 8MB JPEG at 2000 x 1500.

    Where this leaves you

    The memory limit is not one setting with one place to change it. It is a server-enforced ceiling, plus two WordPress constants that politely ask for more and get told no on a meaningful share of shared hosting. Working out which of the three stopped a given request is most of the job, and Site Health plus one peak-usage reading gets you there in a few minutes.

    Decision table matching four measured situations to what actually helps with a WordPress memory limit error, from raising constants to batching the job instead

    Once the real numbers are in front of you, the decision usually makes itself. A request peaking a little over the line needs a modest raise. A request peaking at three times the limit is doing too much in one pass, and no ceiling you can realistically buy will fix it; that is a batching problem wearing a memory error as a costume.

    And if images are what tipped you over, the arithmetic is the thing to carry away. Four bytes per pixel, source and target resident together, repeated once per registered size. Shrink the originals, drop the sizes nobody uses, and the ceiling stops mattering.

     

    Allowed Memory Size Exhausted: Where the WordPress Memory Limit Lives

    Table of Contents

    Learn it by building something

    Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

    One mail a week, and then it ends.
    Unsubscribe in one click.

    WordPress Images

    Clean Up WordPress Media Library Without Breaking Pages

    An attachment is a row in wp_posts; its files sit somewhere else, and deleting one does not delete the other. What to measure before you start, why "unused" is so hard to prove, and the order of operations that keeps the site up.

    SEO & Structured Data

    Redirect Rule Generator: The Rules That Never Fire

    A redirect list rarely breaks. It accumulates: a broad prefix rule that kills every rule below it, a target missing a trailing slash that doubles your hop count, a 301 where a 308 was needed. Generate the Apache, nginx and CSV versions of your rules, then walk a URL through them and watch where it actually goes.

    Developer Tools

    Dummy Text Generator: Lorem Ipsum That Behaves Like Real Copy

    Lorem ipsum has a longest word of thirteen letters and no umlauts at all, so a layout tested with it is tested with the easiest text it will ever hold. What placeholder text should actually prove, and a generator that produces it.

    WordPress Development

    functions.php Explained: What It Is and How Not to Break Your Site With It

    functions.php is a normal PHP file that ships with your WordPress theme and runs on every page load, which makes it powerful, easy to misuse, and the wrong place for anything you want to keep. Here's what it actually does, and how to edit it without taking your site down.

    WordPress Images

    Responsive Hero Images: The Focal Point Decides Everything

    Your hero becomes a 3.2:1 strip on desktop and a portrait crop on phones, and object-fit: cover decides what survives. The formula behind the crop, the CSS that steers it, and a simulator that shows all four breakpoints before you publish.

    SEO & Structured Data

    WordPress Image SEO: What Works and What Is Folklore

    Most image SEO checklists are ordered by how easy each item is to write about. This one ranks the advice by mechanism instead, naming the core function behind every claim, and says plainly which parts are folklore.

    Download the free WunderPaint Plugin for WordPress

    The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

    The Image Editor & Design Studio

    Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

    Free

    Chaos Art

    Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

    Pro

    Particle Strokes

    Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

    Pro

    City Diorama

    Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.

    Free

    Papercut Art

    Layered paper pictures with real depth - a photo sliced along its actual depth into up to twenty layers, parametric landscapes, animals and clouds you can shape, letters with real counters, and a look that runs on three dials.

    Pro

    3D Earth Studio

    A hyperrealistic globe - day and night with city lights, live clouds, atmosphere halo, country borders and highlights, click-to-place markers with flight-route arcs, satellite orbits, seamless rotation video and a live website embed.

    Free

    Mystic Studio

    Turn a birth date into wall art - a real natal chart with houses and aspects, the moon of that night, zodiac and Chinese zodiac posters, numerology cards and a synastry wheel for two, in eight artful themes.

    Free

    Marble Bath

    Marble paper on a virtual water bath - drop, rake and comb real Ebru patterns with gestures, flowers and classic recipes, razor-sharp at any size and re-editable as a layer.

    Free

    Day Ring

    Turn a day into a beautiful circular schedule - colour-coded time blocks as arcs around a 24-hour clock, with concentric rings for overlaps, emoji, templates and a legend.

    Free

    Code Shot

    Turn code into a gorgeous, share-ready image - syntax highlighting, editor themes, window frames and diff highlighting - then drop it into your design as a re-editable layer.

    Pro

    3D Solar System Studio

    Build a date-accurate 3D solar system - real planet positions for any date, photoreal textures and one slider from artistic to true scale - then drop it into your design as an editable layer.

    Pro

    3D Molecule Studio

    Build a real 3D molecule - from a curated library, the periodic table or a SMILES string - then style it, measure it and drop it into your design as an editable layer.

    Pro

    3D Textile Studio

    Drop your design onto cloth that behaves like the material you pick: silk falls soft, felt holds its shape, flag fabric snaps in the wind. Hang it, blow it and drape it, then lay the finished drape back into your document as a picture.

    Pro

    3D Particle Studio

    Point the engine at any layer and it becomes a cloud of particles that keeps its colours, flowing through a sphere, a galaxy or your own outline. Keep the frame you like as a still, or embed the running engine so it keeps moving on your page.

    Free

    Origami

    Put your own picture on the paper and watch that very sheet fold itself into a crane or a box. Every step is a station you can stop at and turn around in 3D, which is exactly where printed diagrams leave you alone.

    Pro

    3D Flip Studio

    A hardcover you can leaf through, a limp magazine, a strewn pile of sheets, a sticker peeling off its backing. The curl is real geometry, so the print never slides across the paper.

    Free

    Handwriting Fonts

    Draw the alphabet here or fill in a printed sheet and photograph it. What comes out is a genuine font family, installed into your site and available in every picker.