Tiago Sampaio

main ai engineering career

engineering

10 min read

How to Fix: “There has been an error processing your request” in Magento 2

Seeing 'There has been an error processing your request' in Magento 2? Use the error log record number to find the real error in minutes.

magento-2-there-has-been-an-error-processing-your-request.png — 1920 × 1080

Cover: How to Fix "There has been an error processing your request" in Magento 2. Panel: HTTP status 500, a 64-character HMAC-SHA256 record number, and grep it in exception.log. Based on Magento 2.4.7-p10, 2.4.8-p5 and 2.4.9.

Your store shows a white page with one line in bold: “There has been an error processing your request”. Below it, a short sentence about security and a long string of letters and numbers. Customers can’t buy, the client is on the phone, and the page tells you nothing about what broke.

This is the most famous error page in Magento 2 and Adobe Commerce. Every developer, every agency and a lot of store owners have seen it. The good news is that the page gives you everything you need to find the real problem. Most people just don’t know where to look.

In this post of the Magento Fix Log, I’ll show what Magento does behind that page, how to turn the record number into the real error in a few minutes, and the settings in pub/errors that most developers never open. Everything here was checked in the code of Magento 2.4.7-p10, 2.4.8-p5 and 2.4.9.

What the page is telling you

This is what a visitor sees:

Shell
There has been an error processing your request

Exception printing is disabled by default for security reasons.

Error log record number: 8f3c1d0e9a7b...  (64 characters)

The page itself is not the problem. It’s what Magento shows when an exception reaches the very top of the application and nobody caught it on the way.

Here’s how it works. In a web request, Magento\Framework\App\Bootstrap::run() catches any \Exception and passes it to Magento\Framework\App\ExceptionHandler::handle(). That method tries five things, in this order:

  1. Developer mode: print the full exception as plain text and stop.
  2. Maintenance mode: show the 503 page from pub/errors/503.php. (If Magento isn’t installed yet, it redirects to setup instead.)
  3. Session exception: redirect the visitor to the home page.
  4. Init exception: show the 404 page from pub/errors/404.php.
  5. Anything else: save a report and show the page you’re looking at.

So if you see this page, three things are true. The store is not in developer mode, an exception escaped all the code that could have handled it, and Magento wrote the details to disk. The response also carries HTTP status 500, set in pub/errors/processor.php, so your uptime monitoring should see it too.

One more detail most people miss. Magento registers its own PHP error handler in Bootstrap, and Magento\Framework\App\ErrorHandler turns PHP warnings and notices (the ones enabled in error_reporting) into exceptions. That means a simple Warning: Undefined array key in a custom module can also end on this page.

The quick fix

If the store is down and you need the short version:

Shell
# 1. Copy the record number from the page, then search the log
grep -n "PASTE_THE_RECORD_NUMBER" var/log/exception.log

# 2. Find the report file, wherever it is
find var/report -type f -name "PASTE_THE_RECORD_NUMBER"

# 3. Read the error message, the trace and the URL
jq -r '.["0"], .["1"], .url' var/report/PASTE_THE_RECORD_NUMBER

The message tells you the real error. Fix that, and the page goes away. The rest of this post explains each step and the traps around them.

Step 1: Search exception.log for the record number

Start here. It works even when all you have is access to the logs.

When ExceptionHandler builds the report, it logs the exception as critical and adds the record number to the log context:

PHP
$reportData['report_id'] = $this->encryptor->getHash(implode('', $reportData));
$this->logger->critical($exception, ['report_id' => $reportData['report_id']]);

Magento’s logger (Magento\Framework\Logger\LoggerProxy) puts the exception object in the context, and the system log handler sends every record with an exception to var/log/exception.log. The log record carries the report_id, and its message is the exception converted to text. That’s PHP’s own format: the class, the message, the file and line, the stack trace, and any previous exceptions in the chain.

So the record number on the page is a search key:

Shell
grep -n "PASTE_THE_RECORD_NUMBER" var/log/exception.log

Open the file at that line and you have the exception class, the message and the trace.

Here’s a detail that makes this even more useful. The record number is not random. It’s an HMAC-SHA256 of the error message, the trace, the URL and the script name, keyed with your store’s encryption key (EncryptorInterface::getHash() without a salt). In practice:

  • The same error on the same URL always gets the same number on that store. Refreshing the page won’t give you a new one.
  • A different URL or a different trace gives a different number.

That turns grep into a quick counter:

Shell
grep -c "PASTE_THE_RECORD_NUMBER" var/log/exception.log

If the count is one, it was probably a one-off. If it’s in the hundreds, customers are hitting it all day.

Step 2: Open the report in var/report

pub/errors/report.php saves the same data to a file named after the record number. By default the path is:

Shell
var/report/PASTE_THE_RECORD_NUMBER

The file is JSON (Magento uses its Json serializer). The keys are a bit unusual, because the data starts as a PHP array with numeric keys:

KeyContent
"0"the exception message
"1"the stack trace as text
urlthe request URI
script_namethe PHP script that handled the request
report_idthe record number

That’s why the jq command uses .["0"] and .["1"]:

Shell
jq -r '.["0"], .["1"], .url' var/report/PASTE_THE_RECORD_NUMBER

No jq on the server? python3 -m json.tool var/report/PASTE_THE_RECORD_NUMBER works too.

Three traps here.

The file might not be in the root of var/report. pub/errors/processor.php supports nested folders built from pairs of characters of the record number. With a nesting level of 2, a report for 44ffb108... is saved in var/report/44/ff/44ffb108.... The level comes from the MAGE_ERROR_REPORT_DIR_NESTING_LEVEL environment variable or from dir_nesting_level in pub/errors/local.xml, from 0 to 32. If ls var/report doesn’t show your file, use the find command above.

The file shows the latest occurrence only. Because the same error gets the same number, Magento writes to the same file again. If you need history, use exception.log.

Function arguments are hidden by default. The trace is built with Debug::trace(), and arguments are only included when the MAGE_DEBUG_SHOW_ARGS environment variable is set. Turn it on only while you debug, because arguments can contain customer data, tokens or passwords.

Step 3: See the whole exception chain in developer mode

The report file has one message and one trace: the top exception. Many Magento errors are wrapped, and the exception that really caused the problem sits in getPrevious(). exception.log already has the full chain. On a copy of the store where you can reproduce the problem, developer mode shows it right in the browser.

In developer mode, ExceptionHandler::handleDeveloperMode() skips the report page and prints every exception in the chain as plain text, starting with a line like 2 exception(s):, then each exception with its class, message and trace.

On a local or staging copy, check the mode and switch it:

Shell
bin/magento deploy:mode:show
bin/magento deploy:mode:set developer

Then reproduce the request and read the whole chain.

Two warnings.

First, don’t do this on production. The full exception, with file paths and queries, goes straight to the visitor’s browser.

Second, check where the mode comes from. Bootstrap::isDeveloperMode() reads MAGE_MODE from the server environment first and only falls back to app/etc/env.php when it’s not set. If nginx or PHP-FPM passes MAGE_MODE, it wins over what deploy:mode:show reports. A store can say “production” in env.php and still answer web requests in developer mode because of one line in a server config.

pub/errors/local.xml: the file most developers never open

The behaviour of this page is controlled by pub/errors/local.xml. A clean install doesn’t have it. It only ships pub/errors/local.xml.sample.

These are the options processor.php reads:

OptionDefaultWhat it does
skindefaultWhich folder in pub/errors renders the page
report/actionemptyEmpty shows “Exception printing is disabled…”. print shows the error message. email sends the error by email
report/subjectStore Debug InformationEmail subject, followed by the record number
report/email_addressemptyWhere the email goes. If empty, email is ignored
report/trashleaveleave keeps the report file. delete removes it after the email is sent
report/dir_nesting_level0Folder nesting for var/report (the environment variable wins)

Now the trap. The sample file sets <action>print</action>. If someone runs cp pub/errors/local.xml.sample pub/errors/local.xml on a live store to “see the error”, every visitor who hits an exception will see the exception message on the page. That’s the security reason the default text talks about.

Also note what print shows: only the message (reportData[0]), not the trace. It helps less than people expect. The log and the report file are better tools.

The email action is interesting for small stores without monitoring, but read the code before you use it:

  • It uses PHP’s mail() function, so the server must be able to send email.
  • An email with the error and the trace is sent every time the error page is rendered with that action, so a busy error can fill an inbox fast.
  • The visitor sees a contact form (“We are currently experiencing some technical issues…”) and can send their name, email and a comment, which also goes to that address.

For a real store, I prefer proper log monitoring. But it’s good to know this exists, especially when you inherit a project and suddenly receive “Store Debug Information” emails.

When you don’t see this page at all

Not every 500 error goes through ExceptionHandler. Knowing the difference saves time.

“An error has happened during application run. See exception log for details.” This plain-text message comes from Bootstrap::terminate(). It appears when the problem is a PHP \Throwable that is not an \Exception (like a TypeError), or when something fails while Magento is handling the first error. Same advice: go to var/log/exception.log.

A blank page or your web server’s own 500 page. Fatal errors such as Allowed memory size exhausted stop PHP, and Magento’s handlers can’t catch them. Look at the PHP-FPM and web server error logs.

“Error 503: Service Unavailable”. That’s maintenance mode (var/.maintenance.flag), handled before the report, by the same ExceptionHandler.

The 404 page from pub/errors. That’s a Magento\Framework\Exception\State\InitException, which ExceptionHandler handles before the report. It’s logged as critical, so it’s in exception.log too.

Checklist before you close the ticket

  • Did you search var/log/exception.log for the record number and read the full trace?
  • Did you count how many times it happened, to understand the impact?
  • Did you check the previous exceptions, not only the top message?
  • Is pub/errors/local.xml absent on production, or at least not set to print?
  • If you turned on MAGE_DEBUG_SHOW_ARGS or developer mode to debug, did you turn it off again?
  • Is your monitoring alerting on HTTP 500 and on new lines in exception.log?
  • Is someone cleaning var/report? With trash set to leave, Magento’s error page code never deletes those files.

I’ve worked with Magento for more than 15 years, and my rule for this page is simple: the record number is the ticket number. The answer is always in the log. You just need to know which file and which line.

What’s the strangest error you found behind this page?

Sources

All code references were checked in Magento Open Source / Adobe Commerce 2.4.7-p10, 2.4.8-p5 and 2.4.9 (2.4.7-p10 from the open source magento/magento2 repository on GitHub, the other two from full Adobe Commerce installs):

  • vendor/magento/framework/App/Bootstrap.php: run(), terminate(), isDeveloperMode()
  • vendor/magento/framework/App/ExceptionHandler.php: handle(), handleDeveloperMode(), handleGenericReport()
  • vendor/magento/framework/App/ErrorHandler.php: PHP warnings and notices turned into exceptions
  • vendor/magento/framework/Encryption/Encryptor.php: getHash() and hash()
  • vendor/magento/framework/Logger/LoggerProxy.php, Logger/Handler/System.php, Logger/Handler/Base.php and app/etc/di.xml: how the error reaches exception.log
  • vendor/magento/framework/Debug.php: trace() and MAGE_DEBUG_SHOW_ARGS
  • pub/errors/report.php, pub/errors/processor.php, pub/errors/local.xml.sample and pub/errors/default/report.phtml

Written by

TS

Tiago Sampaio

Adobe Commerce & Magento 2 Expert

Senior developer with 15+ years building software. Writes practical fixes for Magento 2 and Adobe Commerce, and honest notes on growing as a developer.

$ git remote add inbox

Get new commits by email

New posts from both branches, engineering and career. No spam, unsubscribe any time.

$ git notes show

Leave a Reply