Apache - PHP memory errors

Posted on 2011-03-02
Last Modified: 2012-06-27
I'm new to administering a LAMP server, and I'm having PHP memory issues.

This server is used to run Moodle 2. It's a memory hungry PHP application.

Here's the deal:  I had this setup working (albeit a little slow) on a shared hosting account without any visible memory issues. (I did have MySQL errors once in a while which is why we went to a VPS.) I can't imagine that they would have allocated more resources than what I have, so I believe this is a configuration error on my part.  How can I get this to run well? I have other detailed questions below, but that's the main one.

I have the following set up:

CentOS 5.5 64 bit VPS
1 Gb RAM
Apache 2.2.17
PHP 5.3.5
APC (Alternative PHP Cache) installed but I hope disabled

I believe that APC is disabled because I used to get different errors in the log.

Here's a link to my PHP info: PHP settings

I've embedded some of my httpd.conf below.
## Server-Pool Size Regulation (MPM specific)

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# ServerLimit: maximum value for MaxClients for the lifetime of the server
# MaxClients: maximum number of server processes allowed to start
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule prefork.c>
StartServers       8
MinSpareServers    5
MaxSpareServers   20
ServerLimit      256
MaxClients        35
MaxRequestsPerChild  3000

# worker MPM
# StartServers: initial number of server processes to start
# MaxClients: maximum number of simultaneous client connections
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule worker.c>
StartServers         2
MaxClients          50
MinSpareThreads     25
MaxSpareThreads     75 
ThreadsPerChild     25
MaxRequestsPerChild  0

Open in new window

I believe that Apache is using the prefork MPM because 8 apache servers show up in top when I restart Apache . Should I be using the worker MPM? How do I do that?

How should I set  PHP's memory limit? Does the memory limit inflate each Apache server? (i.e. PHP memory = 128M,  apache server size becomes 128M + whatever apache needs to run)

Eventually I'd like to enable APC or another PHP accelerator.  How should I do that?

Thank you!
Question by:eglescout
  • 4
  • 4
LVL 50

Expert Comment

by:Steve Bink
ID: 35024164
Is PHP running as SAPI, CGI, or FastCGI?  What is the allowed memory per thread?  If you're running Fast-/CGI, how many PHP threads are you allowing?

This also depends on how much memory MySQL is using, and how it is performing under those conditions.  What configuration settings do you have for it?

I would disagree with your assumption they did not allocate as much resources to you under shared.  Well, not necessarily, anyways.  Under your shared environment, your web service may have been constrained to a particular amount of memory, but that would have been dedicated to the PHP application itself, not the web service threads and MySQL.  In other words, your whole application would have had access to more resources than what was strictly defined in your plan.  In your VPS environment, your single server instance needs to pack all of that execution into a single gig of memory.  That is going to be a pretty tight fit under the best of circumstances.  You should look at increasing the amount of memory in the VPS.
LVL 50

Expert Comment

by:Steve Bink
ID: 35024188
I just noticed your link to the PHP settings.  Your phpinfo() output shows 256M per thread, which means, with no consideration for the OS or other applications, you could have 4 threads running.  Any more than that and you start paging things off, taking a huge hit to performance.  Once the paging is full, or if it is not an option in your environment, you will see service denials, or existing services getting shut down due to lack of memory.

I maintain my previous recommendation - get more memory in your VPS.  In addition, start looking towards optimization of your PHP runtime environment.  Run some benchmarks to find out exactly what your applications require as they run their high-load pages, and set your limits accordingly.  Finally, be sure to take a look at your MySQL memory requirements.

Author Comment

ID: 35027081
Being new to this, I'm not sure how to tell which way PHP is running. How would I figure this out?

From top, here's what is allocated to mysql:
VIRT: 358m
RES:  41m

What would you suggest for benchmarking?

Free learning courses: Active Directory Deep Dive

Get a firm grasp on your IT environment when you learn Active Directory best practices with Veeam! Watch all, or choose any amount, of this three-part webinar series to improve your skills. From the basics to virtualization and backup, we got you covered.

LVL 28

Expert Comment

by:Jan Springer
ID: 35027314
I agree with routinet.  If you have enough physical memory in the box and can spare more toward php -- increase memory_limit in php.ini.
LVL 50

Accepted Solution

Steve Bink earned 250 total points
ID: 35033836
>>> I'm not sure how to tell which way PHP is running.

By looking at your phpinfo() results.  You are currently running as an Apache SAPI handler.

The benchmarks you need come from several different areas.  For example, your top report for MySQL is fine, but it needs to be compared against actual MySQL performance, what kind of load the server is receiving (queries and connections), etc.  It may be that your database requirements are simple and do not need a lot of memory to provide the performance your application wants, which means you can limit the memory allocated to the service.  Or maybe you have a lot of data with inefficient indexing, which means you need to optimize your design and provide more memory/CPU power.

In PHP, you can monitor the amount of memory used, the peak memory required, and the execution time of each script.  Benchmark the overall requirements, as well as individual, high-execution portions of your code.  I doubt your threads need as much as 256MB to execute, so knowing what your highest load pages are will be valuable in determining your memory_limit value, which in turn determines how many concurrent requests can be made with the resources you have.

Optimization is not a simple task.  It is often a long, arduous road of trial and error, tweaking obscure configuration settings, and watching results over time.  There are literally hundreds, if not thousands, of data points that can affect how your application performs.

Author Comment

ID: 35037482
Thanks routinet for getting me started.

Here's my first round of optimization:
I'm going to bump up the memory to 2G this weekend.
I have someone who's going to help me get PHP running as a fast cgi process. That'll include some apache tuning in there too.  Any pointers for that?

In PHP, you can monitor the amount of memory used, the peak memory required, and the execution time of each script.

How do I do that?

I realize that optimization is a long term thing.  I appreciate you pointing out the tools to use.  Other than to use top and apache bench, I'll feel like I'm pretty much in the dark.
LVL 50

Expert Comment

by:Steve Bink
ID: 35041796
My current projects do not have high loads or high traffic, so I'm a bit out of touch due to lack of necessity.  I'll let some other experts pick up the Apache tuning questions, and I'll be paying just as much attention as you are.  Also, I have only recently started using FastCGI, so do not have much practical experience with tuning it.  From my experience so far, it appears my normal CGI configurations work just as well with FastCGI.  

For PHP optimization, everything depends on just exactly what you are running.  For memory limitations, I use a general rule 125-150% of the highest load page, though certain applications might tend towards extravagant outlying requirements (e.g., a management app that pulls enormous datasets).  So if I'm running through my benchmarks and I see a page use 8mb of memory (peak), then I'll set the memory_limit to 12mb.  I also use an alerting system in my applications to log whenever a page's peak usage reaches 80% of memory_limit, and either investigate or adjust accordingly.  Aside from creating a ceiling for a thread's memory usage, this setting does not so much optimize performance of PHP as it does impact how much concurrent performance you can see from the server itself.  You should account for any base frameworks you are using (base memory usage/footprint), and have an idea what your application within that framework will require.  Here are some PHP functions you should become acquainted with:

Here's a nice little tutorial on how to use microtime to create benchmarks within your app:

The memory functions are very much the same concept, just with memory usage instead of execution time.

If you're using a framework, keep in mind that the framework itself not only requires a specific minimum footprint, but will typically increase the resource cost of every operation.  For example, setting a global variable in base PHP uses only the memory required to hold the data.  Setting it in a framework could require much more depending on data validation, escaping, and propagation through object hierarchies.

Assisted Solution

eglescout earned 0 total points
ID: 35062121
Thanks routinet.  I did find out that Moodle 2.0 with the Decaf theme will peak out around 152M for a couple of our page views.  I would not say this is extremely optimized code, but that's what it is at the moment. Considering I update this weekly via Moodle CVS, I'm hoping others are working to optimize the code.

Here's what happened this weekend:
Set up fastcgi - fcgid actually.  Pretty much followed the following 'recipe':

added the following to fcgid.conf:
# These are the maximum processes for the whole web server
MaxProcessCount 60
# Default maximum processes per virtual host
DefaultMaxClassProcessCount 30

Open in new window

My vhost file now looks like this:
<VirtualHost *:80>
    ServerAdmin [email]
    DocumentRoot /var/www/vhosts/[your web root]
    ServerAlias [your choice]
    ErrorLog /var/www/vhosts/[your vhost directory]/error_log
    CustomLog /var/www/vhosts/[your vhost directory]/logs/access_log combined

    UseCanonicalName Off

   <Directory /var/www/vhosts/[your web root]>
        AllowOverride none
        Options ExecCGI FollowSymLinks
        Order allow,deny
        Allow from all
        Deny from none

        AddHandler fcgid-script .php
        ## use the following if you have different virtual hosts running FastCGI
        #SuexecUserGroup domainuser psacln
        FCGIWrapper /usr/bin/php-cgi .php

      # Insert filter
          SetOutputFilter DEFLATE

          # Netscape 4.x has some problems.
          BrowserMatch ^Mozilla/4 gzip-only-text/html

          # Netscape 4.06-4.08 have some more problems
          BrowserMatch ^Mozilla/4\.0[678] no-gzip

          # NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
          # the above regex won't work. You can use the following
          # workaround to get the desired effect:
          BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

          # Don't compress images, videos and audio
          SetEnvIfNoCase Request_URI \
          \.(?:gif|jpe?g|png|flv|mp4|m4v|mp3|zip|gz)$ no-gzip dont-vary

          # Make sure proxies don't deliver the wrong content
          Header append Vary User-Agent env=!dont-vary


#  Allows relative paths
   AcceptPathInfo on

   ExpiresActive On
   ExpiresByType text/html "access plus 2 seconds"
   ExpiresDefault "access plus 2 seconds"
   ExpiresByType image/gif "access plus 2 months"
   ExpiresByType image/jpeg "access plus 2 months"
   ExpiresByType image/png "access plus 2 months"
   ExpiresByType application/x-shockwave-flash "access plus 2 months"
   ExpiresByType text/css "access plus 4 weeks"
   ExpiresByType text/javascript "access plus 2 weeks"
   ExpiresByType application/x-javascript "access plus 2 weeks"
   ExpiresByType text/xml "access plus 2 seconds"

#   FileETag MTime Size
   FileETag None


Open in new window

Set my prefork processes to start @ 15 and expand to 30.
Did some tuning to MySQL using the following script:

Set the MySQL connections 5 more than MaxProcessCount in fcgid.conf.

This seems to have worked pretty well.

I'll see how far I get into PHP based optimization.


Author Closing Comment

ID: 35239197
This would have been better asked in multiple questions.

Featured Post

PRTG Network Monitor: Intuitive Network Monitoring

Network Monitoring is essential to ensure that computer systems and network devices are running. Use PRTG to monitor LANs, servers, websites, applications and devices, bandwidth, virtual environments, remote systems, IoT, and many more. PRTG is easy to set up & use.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Nothing in an HTTP request can be trusted, including HTTP headers and form data.  A form token is a tool that can be used to guard against request forgeries (CSRF).  This article shows an improved approach to form tokens, making it more difficult to…
This article discusses four methods for overlaying images in a container on a web page
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…
This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.

830 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question