What is the maximum amount of memory that a php script executed by apache?

OWASP 2013-A5 OWASP 2017-A6 OWASP 2021-A5 CAPEC-346 CWE-213 ISO27001-A.18.1.3 WASC-13

The memory limit is a setting that allocates a fixed memory size for executing a script. This setting protects the server from poorly coded scripts that allocate unnecessary space, in return saving the server’s memory during large-scale usage of the application. An attacker can view the memory limit set by executing phpinfo() function to see the memory limit and plan an attack according to the value. A server administrator can set memory limit from -1 (No memory allocation) to any size. The memory limit was first introduced as a setting in php.ini after PHP version 5.2.0. For PHP versions before 5.2.0, the memory limit was implemented during compile time. The memory limit was implemented as a per-script setting.

If an attacker knows the maximum size a PHP script allocated by the server, he will be able to execute malicious scripts with maximum size from different clients. This malicious move by the attacker can hang the server to perform a denial of service attack. The attacker can also perform other attacks after this attack to completely shut down the server.

Example The following code is an example of PHP memory_limit:-

php.ini

.htaccess

        php_value memory_limit 512M

    

Impact

Using this vulnerability, an attacker can perform:-

  • Denial of service attacks.
  • Illegal termination of the program to void the validity of the data.

Mitigation / Precaution

Beagle recommends the following fixes:-

Make sure the phpinfo() is disabled in the server. Change the memory limit to your desired value. php.ini

.htaccess

        php_value memory_limit 100M

    

Check your website security today and

identify vulnerabilities before hackers exploit them.

How to check how much memory your PHP scripts use: a tutorial with examples

How much memory does your PHP script use?

Memory efficiency is not among the first things to learn about PHP, but checking how much memory a PHP script is using can be very useful.

Learning about memory and code optimization is also among the steps I recommend in my free guide to become a better PHP developer.

Keep reading to learn how to check your PHP scripts memory usage and to see some specific examples.

WHY YOU SHOULD CHECK YOUR SCRIPTS MEMORY USAGE

Among web applications requirements, memory efficiency is not usually at the top of the list.

And, generally speaking, that’s fine.

Other factors, like SQL security and authentication good practices, are surely more important.

However, excessive memory consumption can be a real problem.

In fact, memory consumption issues can easily lead to crashes, hard-to-find bugs and even open the door to Denial-of-Service attacks.

This is especially important in high traffic websites, where many concurrent script executions occur and it’s easier for memory-related issues to cause problems.

Before moving to the next part, I suggest you look at the following short video, which explains nicely how memory management works in PHP and gives you some basics tips to optimize your code:

Some kinds of PHP scripts are particularly at risk of excessive memory consumption:

  • very complex scripts, especially if including nested loops;
  • scripts using external resources like XML files, database result sets and remote connections;
  • always-on scripts like PHP daemons.

Of course, memory issues can occur in other kinds of script as well.

But how can you check how much memory your script is using?

It’s easy:

the standard PHP library provides you with two functions that give you that exact information: memory_get_usage() and memory_get_peak_usage().

Let’s see how they work. 

[easy-tweet tweet="A tutorial (with examples) on how to check your PHP script memory usage" via="no" hashtags="PHP"]

memory_get_usage() returns the number of bytes used by the script when the function is called.

Similarly, memory_get_peak_usage() returns the maximum amount of memory (again, in bytes) used by the script until the function is called.

For example:

<?php

$big_array = array();

for ($i = 0; $i < 1000000; $i++)
{
   $big_array[] = $i;
}

echo 'After building the array.<br>';
print_mem();

unset($big_array);

echo 'After unsetting the array.<br>';
print_mem();


function print_mem()
{
   /* Currently used memory */
   $mem_usage = memory_get_usage();
   
   /* Peak memory usage */
   $mem_peak = memory_get_peak_usage();

   echo 'The script is now using: <strong>' . round($mem_usage / 1024) . 'KB</strong> of memory.<br>';
   echo 'Peak usage: <strong>' . round($mem_peak / 1024) . 'KB</strong> of memory.<br><br>';
}

As you see, after the array is unset the current memory usage goes down to 370Kb, but the peak usage remains the same.

(If you want to learn how to set up a local PHP environment, see the Getting started chapter of my PHP learning program).

Both functions take an optional boolean argument named $real_usage. Its default value is false.

What’s it’s meaning and when should you set it to true?

Let’s find out.

Would you like to talk with me and other developers about PHP and web development? Join my Facebook Group: Alex PHP café

See you there 🙂

THE $real_usage ARGUMENT: HOW TO USE IT

$real_usage is a boolean argument. Its default value is false for both memory_get_usage() and memory_get_peak_usage().

If this argument is set to true, then the function returns the full amount of memory allocated by the PHP interpreter for the script instead of the memory actually used by the script.

What’s the difference…?

This is how it works:

The PHP interpreter dynamically allocates memory for the script. This memory is allocated in chunks, small pieces of a fixed amount of memory.

When all the allocated memory has been used by the script, the interpreter allocates another chunk.

So:

  • first, the interpreter reserves a chunk of memory for the script to use;
  • then the script gradually fills it with its data, until the chunk is full;
  • if the script needs more memory, the interpreter allocates another chunk of memory;
  • repeat…

Therefore, the amount of allocated memory is always a bit bigger than the amount of used memory (how much bigger depends on many factors, including the PHP configuration and the operating system settings).

This is what $real_usage is used for:

  • If $real_usage is set to false (the default), then the functions return the used memory only.
  • If $real_usage is set to true, then the functions return the whole allocated memory.

The following example shows how the allocated memory grows along with the memory actually used by the script:

<table align="center" valign="top">
<tr>
<td>Used memory</td>
<td>Allocated memory</td>
<td>Difference</td>
</tr>

<?php

$big_array = array();

for ($i = 0; $i < 100000; $i++)
{
   $big_array[] = 'aaa' . $i;
   
   if (($i % 7000) == 0)
   {
      /* With $real_usage = false (the default value) */
      $used_mem = round(memory_get_usage(false) / 1024);
      
      /* With $real_usage = true */
      $alloc_mem = round(memory_get_usage(true) / 1024);
      
      echo '<tr><td style="color: green; border: 1px solid #888;">' . $used_mem . 'KB</td>';
      echo '<td style="color: blue; border: 1px solid #888;">' . $alloc_mem . 'KB</td>';
      echo '<td style="color: red; border: 1px solid #888;">' . ($alloc_mem - $used_mem) . 'KB</td></tr>';
   }
}

Notice how the used memory (the first column) grows gradually, while the allocated memory (the second column) grows in chunks.

Note:

if you try running this example and get strange results, like the allocated memory being smaller than the used memory, it’s probably because of some caching system.

A way to avoid such problems is to restart the web server right before running the script.

When should you set $real_usage to true?

Here is the rule of thumb:

if you want to see how much system memory your script is using, then you should set $real_usage to true.

Why?

Because the allocated memory, even if it’s not actually used by the script, is still reserved for it and isn’t available for other system processes.

In other words, it is the amount of system RAM reserved for the PHP process.

On the other hand, if you want to profile your PHP script, then it’s better to leave it to false because you will see the memory usage in more detail.

In fact, that way you can see how much memory a specific piece of code is using regardless of how much memory is available to the script as a whole.

Let’s see a couple of examples of how to do this. 

HOW TO MONITOR LOOPS

Inside loop structures excessive memory usage can lead to problems, especially if this usage keeps growing after each loop iteration.

If you want to make sure a loop isn’t using too much memory, it’s a good idea to profile it.

How can you do it?

To begin with, you should print the memory usage before and after the loop to check the difference.

If you see that the memory usage is too high, then you should also check inside the loop itself.

The $real_usage parameter is better left to false, because you want to profile your code in detail more than your whole script.

It’s also useful to see how much of the maximum available memory is being used.

A PHP script can use only up to a certain amount of memory. This value is set in the memory_limit variable of the php.ini file (the main PHP configuration file).

You can retrieve that value with the ini_get() function, convert it into bytes and compare it to the script’s memory usage.

Here’s an example:

<?php

$mem_usage = memory_get_usage();
echo 'Memory usage before the loop: <strong>' . round($mem_usage / 1024) . 'KB</strong><br>';

/* Get the memory limit in bytes. */
$mem_limit = get_memory_limit();
echo 'Memory limit: <strong>' . round($mem_limit / 1048576) . 'MB</strong><br>';

$array = array();

for ($i = 0; $i < 100000; $i++)
{
   $array[] = $i;
   
   /* Check the memory usage every 10000 iterations. */
   if (($i % 10000) == 0)
   {
     $mem_usage = memory_get_usage();
     $limit_perc = $mem_usage * 100 / $mem_limit;
     
     echo 'Memory usage after iteration ' . $i . ': <strong>' . round($mem_usage / 1024) . 'KB</strong> ';
     echo '(' . round($limit_perc) . '% of the available memory for the script)<br>';
   }
}

$mem_usage = memory_get_usage();
echo 'Memory usage after the loop: <strong>' . round($mem_usage / 1024) . 'KB</strong><br>';


/* Parse the memory_limit variable from the php.ini file. */
function get_memory_limit()
{
   $limit_string = ini_get('memory_limit');
   $unit = strtolower(mb_substr($limit_string, -1 ));
   $bytes = intval(mb_substr($limit_string, 0, -1), 10);
   
   switch ($unit)
   {
      case 'k':
         $bytes *= 1024;
         break 1;
      
      case 'm':
         $bytes *= 1048576;
         break 1;
      
      case 'g':
         $bytes *= 1073741824;
         break 1;
      
      default:
         break 1;
   }
   
   return $bytes;
}

HOW TO MONITOR PHP DAEMONS

PHP daemons are never-ending, looping scripts.

They are extremely useful for maintenance and monitoring tasks, like automatic email notification systems and maintenance operations.

Just like any always-on application, PHP daemons too can suffer from memory leaks.

A good practice is to check the memory usage at the beginning of each loop and do something if it turns out to be excessively high.

For example, you can kill or pause the daemon and send a notification email (you will see how to do it in the next chapter).

For this purpose, it’s better to set $real_usage to true, because you are now more interested in how much memory is really been used more than in profiling your script.

Of course, should memory issues occur, you will need to carefully profile the script to see where the problem comes from (for that, it will be better to leave $real_usage to false).

As an example, let’s see how to terminate a daemon if the used memory is more than 50Mb:

<?php

/* Remove the execution time limit */
set_time_limit(0);

/* Iteration interval in seconds */
$sleep_time = 30;

/* Memory limit (50Mb) */
$mem_limit = 52428800;

$array = array();
$iteration = 0;

while (TRUE)
{
   $iteration++;
   
   /* Memory check */
   if (memory_get_usage(true) >= $mem_limit)
   {
      echo 'Memory limit exceeded after iteration ' . strval($iteration);
      die();
   }
   
   /* Sleep for the iteration interval */
   sleep($sleep_time);
   
   for ($i = 0; $i < 500000; $i++)
   {
      $array[] = $i;
   }
}

You can also stop the daemon when its memory usage goes beyond certain percentage of the maximum allowed usage.

To do this, you need to compare the memory usage to the memory_limit value, just as you saw in the loop example before.

Here’s the example:

<?php

/* Remove the execution time limit. */
set_time_limit(0);

/* Get the memory limit in bytes. */
$mem_limit = get_memory_limit();

/* Iteration interval in seconds. */
$sleep_time = 5;

/* Max memory usage: 10% of the memory_limit. */
$mem_perc_limit = 20;

$array = array();
$iteration = 0;

while (TRUE)
{
   $iteration++;
   
   /* Memory check. */
   $mem_usage = memory_get_usage(true);
   $perc_usage = $mem_usage * 100 / $mem_limit;
   
   if ($perc_usage >= $mem_perc_limit)
   {
      echo 'Memory limit exceeded after iteration ' . $iteration . ' (' . round($perc_usage) . '%)';
      die();
   }
   
   /* Sleep for the iteration interval */
   sleep($sleep_time);
   
   for ($i = 0; $i < 500000; $i++)
   {
      $array[] = $i;
   }
}


/* Parse the memory_limit variable from the php.ini file. */
function get_memory_limit()
{
   $limit_string = ini_get('memory_limit');
   $unit = strtolower(mb_substr($limit_string, -1 ));
   $bytes = intval(mb_substr($limit_string, 0, -1), 10);
   
   switch ($unit)
   {
      case 'k':
         $bytes *= 1024;
         break 1;
      
      case 'm':
         $bytes *= 1048576;
         break 1;
      
      case 'g':
         $bytes *= 1073741824;
         break 1;
      
      default:
         break 1;
   }
   
   return $bytes;
}

HOT TO GET EMAIL NOTIFICATIONS WHEN TOO MUCH MEMORY IS USED

Sometimes, debugging and profiling a PHP script’s memory usage can be difficult.

It’s not always easy to retrieve the output from memory_get_usage() and memory_get_peak_usage(), especially when the memory usage depends on the request string values and other factors.

In some cases, memory issues may arise only under specific circumstances or only after some time (especially in PHP daemons).

Just echoing the memory usage isn’t always good enough.

You can’t just stare at your screen waiting for a memory leak to happen, right?

 One solution is to log the memory usage information to a log file or a database.

But this solution isn’t perfect either, because you will end up with tons of logs that you need to read and manage.

A smarter solution is to make PHP do the hard work.

Instead of printing or logging everything, just check whether the memory usage is too high.

If not, continue the script execution. Otherwise, send yourself a notification email together with useful debugging information.

This simple procedure removes the need for you to actively check your scripts.

In fact, the scripts themselves will tell you when there’s something wrong 😉

Much better, isn’t it?

So, how can this be done in practice?

Let’s go back at the daemon example.

Now, before killing the script when the memory usage is too high, we send an email so that we know that this script has been terminated.

We also attach a file with the debug back trace and one with all the defined variables to make easier for us to debug the issue.

NOTE: you need to install PHPMailer first. Follow my PHPMailer tutorial to learn how to do it.

Here’s the code:

<?php

/* PHPMailer */
use \PHPMailer\PHPMailer\PHPMailer;
use \PHPMailer\PHPMailer\Exception;
require 'C:\xampp\htdocs\PHPMailer-master\src\Exception.php';
require 'C:\xampp\htdocs\PHPMailer-master\src\PHPMailer.php';
require 'C:\xampp\htdocs\PHPMailer-master\src\SMTP.php';

/* Remove the execution time limit */
set_time_limit(0);

/* Iteration interval in seconds */
$sleep_time = 1;

/* Memory limit (50Mb) */
$mem_limit = 52428800;

$array = array();
$iteration = 0;

while (TRUE)
{
   $iteration++;
   
   /* Memory check */
   if (memory_get_usage(true) >= $mem_limit)
   {
      send_debug_email();
     echo 'This PHP script has been killed for using too much memory. An email has been sent.';
      die();
   }
   
   /* Sleep for the iteration interval */
   sleep($sleep_time);
   
   for ($i = 0; $i < 500000; $i++)
   {
      $array[] = $i;
   }
}

function send_debug_email()
{
   $mail = new PHPMailer(true);

   try {
      
     /* Source address */
      $mail->setFrom('[email protected]');
      
      /* Destination address */
      $mail->addAddress('[email protected]');
      
      /* Subject */
      $mail->Subject = 'PHP script memory error';
      
      /* Message */
      $mail->Body = 'This PHP script has been killed for using too much memory.';
      
      /* Add debug info as attachment */
      $mail->addStringAttachment(print_r(debug_backtrace(), true), 'debug_backtrace.txt');
      $mail->addStringAttachment(print_r(get_defined_vars(), true), 'defined_vars.txt');
      
      /* Use SMTP. */
      $mail->isSMTP();
      
      /* SMTP server adrress. */
      $mail->Host = 'mysmtp_server';

      /* Use SMTP authentication. */
      $mail->SMTPAuth = TRUE;
      
      /* Set the encryption system. */
      $mail->SMTPSecure = 'tls';
      
      /* SMTP authentication username. */
      $mail->Username = '[email protected]';
      
      /* SMTP authentication password. */
      $mail->Password = 'smtp_passwd';
      
      /* Set the SMTP port. */
      $mail->Port = 587;

      /* Send the mail. */
      $mail->send();
   }
   catch (Exception $e)
   {
      echo $e->errorMessage();
   }
   catch (\Exception $e)
   {
      echo $e->getMessage();
   }
}

A CAVEAT: RESOURCES

There is one problem with memory_get_usage() and memory_get_peak_usage():

they do NOT take resources into account.

PHP resource types include variables handled by external libraries or by the operating system, including:

  • remote connections (HTTP, FTP…)
  • XML files created with SimpleXML
  • database connections
  • …and more

The system memory for these types is not handled by the PHP core engine itself.

It’s the external library that does that.

Therefore, memory_get_[peak]_usage() does not include it in its result and you need to retrieve it in some other way.

This post from drib tech clearly explains this situation and also provides a solution:

Get the real amount of memory allocated by the PHP

The solution you find in the above post works on Linux systems only.

But don’t worry:

if you are using Windows, you can use the following code that relies on the tasklist windows tool:

<?php

/* Load a big XML file to see the memory usage difference */
$xml_file = 'C:\xampp\htdocs\big_xml_file.xml';
$xml = simplexml_load_file($xml_file);

if ($xml === false)
{
   die('Unable to load and parse the XML file: "' . $xml_file . '"');
}

/* Tasklist command to get info about the process ID running this script */
$cmd = 'tasklist /fi "pid eq ' . strval(getmypid()) . '"';

/* Get the output from tasklist */
$tasklist = trim(exec($cmd, $output));

/* Parse the output to get the memory usage value */
$mem_val = mb_strrchr($tasklist, ' ', TRUE);
$mem_val = trim(mb_strrchr($mem_val, ' ', FALSE));
$mem_val = str_replace('.', '', $mem_val);

echo 'Memory usage from <em>memory_get_usage()</em>: ' . round(memory_get_usage(true) / 1024) . 'KB<br>';
echo 'Memory usage from <em>tasklist</em>: ' . $mem_val . 'KB<br>';

CONCLUSION

Now you know why it’s important to keep your scripts memory usage under control, and you know how to do it.

You learned the difference between memory_get_usage() and memory_get_peak_usage() and what the $real_usage argument really means.

You also saw how to include external resource types in the calculation, on both Linux and Windows systems.

Now I’m curious:

how much memory is the last PHP script you wrote using?

Go check it and tell me in the comments below!

If this tutorial has been helpful to you, please spend a second of your time to share it… thanks!

Do you want to improve your PHP skills?

Grow your PHP skills every week, with my free weekly PHP tips.

What should PHP memory limit be?

A typical appropriate memory limit for PHP running Drupal is 128MB per process; for sites with a lot of contributed modules or with high-memory pages, 256MB or even 512MB may be more appropriate.

How do I check my PHP memory limit?

As long as your array $phpinfo['PHP Core']['memory_limit'] contains the value of memory_limit , it does work the following:.
The last character of that value can signal the shorthand notation. ... .
The beginning of the string is converted to a number in PHP's own specific way: Whitespace ignored etc..

What is a memory limit on a website?

Your website will have a max limit of 1024 MB (1 GB) when accessed through the webserver over http or https, and when you reach that limit, it's time to start working on your application to fix the non-scalable issues with it.

How do I monitor PHP memory usage?

The memory_get_usage function can be used to track the memory usage. The 'malloc' function is not used for every block required, instead a big chunk of system memory is allocated and the environment variable is changed and managed internally. The above mentioned memory usage can be tracked using memory_get_usage().