Key Points
A few years ago, the Apache Foundation's web server (referred to as "Apache") was so common that it became synonymous with the word "Web server". Its daemon on Linux system is named httpd (only means http process) and is preinstalled in the main Linux distribution.
It was originally published in 1995 and quoted Wikipedia as saying, "It played a key role in the early development of the World Wide Web." According to W3techs, it is still the most commonly used web server software. However, its market share is declining based on those reports that show some trends over the past decade and comparisons with other solutions. Netcraft and Builtwith provide slightly different reports, but both agree that Apache's market share is on a downward trend, while Nginx's market share is growing.
Nginx (pronounced engine x) was released by Igor Sysoev in 2004, with its clear goal to surpass Apache. Nginx's website has an article worth reading that compares these two technologies. At first it was primarily used as a complement to Apache, mainly for serving static files, but it has been growing steadily as it continues to evolve to handle various web server tasks.
It is commonly used as a reverse proxy, load balancer, and HTTP cache. CDN and video streaming providers use it to build content delivery systems that are critical to their performance.
Apache has been around for a long time and has many modules to choose from. The management of Apache servers is well known to be user-friendly. Dynamic module loading allows different modules to be compiled and added to the Apache stack without recompiling the main server binary. Typically, modules will be in the Linux distribution repository, and after they are installed, they can be added to the stack gracefully using a2enmod-like commands through the system package manager. Nginx has not seen this flexibility yet. When we look at the guide to setting up Nginx to support HTTP/2, the module is what Nginx needs to build - configured at build time.
Another feature that contributes to the dominance of Apache market is the .htaccess file. It is Apache's killer, making it the preferred solution for shared hosting environments, as it allows control of server configuration at the directory level. Each directory on the server provided by Apache can have its own .htaccess file.
Nginx not only does not have an equivalent solution, but also prevents this usage due to performance impact.
Server manufacturer market share from 1995 to 2005. Data from Netcraft
LiteSpeed or LSWS is a server competitor whose level of flexibility is comparable to Apache without sacrificing performance. It supports Apache-style .htaccess, mod_security, and mod_rewrite, and is worth considering for sharing settings. It is planned as a direct replacement for Apache and can be used with cPanel and Plesk. It has supported HTTP/2 since 2015.
LiteSpeed has three license levels: OpenLiteSpeed, LSWS Standard, and LSWS Enterprise. Standard and Enterprise come with an optional caching solution that rivals Varnish, LSCache, which is built into the server itself and can be controlled using rewrite rules in the .htaccess file (each directory). It also has some DDOS mitigation "battery" built in. This combined with its event-driven architecture makes it a strong contender, targeting performance-focused hosting providers, but it's worth setting up even for smaller servers or websites.
When optimizing a system, we cannot overemphasize the attention to the hardware settings. No matter which solution we choose for the setup, having enough RAM is crucial. When a web server process or an interpreter like PHP does not have enough RAM, they start swapping, and swapping actually means using a hard disk to replenish RAM memory. The effect of this is to increase latency every time this memory is accessed. This reminds us of the second point - hard disk space. Using fast SSD storage is another key factor in website speed. We also need to pay attention to CPU availability and the physical distance between the server data center and the target audience.
To have a deeper understanding of the hardware aspects of performance tuning, Dropbox has a good article.
A practical way to monitor the performance of the current server stack in detail is htop, which is suitable for Linux, Unix, and macOS and provides a colorful overview of our processes.
Other monitoring tools include New Relic (an advanced solution with a full set of tools) and Netdata (an open source solution that provides excellent scalability, fine-grained metrics and customizable web dashboards for small VPS System and server network monitoring). It can send alerts for any application or system process via email, Slack, pushbullet, Telegram, Twilio, etc.
Monit is another headless open source tool that monitors the system and can be configured to alert us when certain conditions are met, or restart certain processes, or restart the system.
AB (Apache Benchmark) is a simple load testing tool from the Apache Foundation, and Siege is another load testing program. This article explains how to set them up, here are some more advanced tips about AB, and a deeper understanding of Siege can be found here.
If you prefer the web interface, Locust is a Python-based tool that is very convenient for testing website performance.
After installing Locust, we need to create a locustfile in the directory from which we will launch it:
<code>from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") @task(2) def shop(self): self.client.get("/?page_id=5") @task(3) def page(self): self.client.get("/?page_id=2") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 3000 </code>
Then we just start it from the command line:
<code>locust --host=https://my-website.com </code>
A warning about these load testing tools: they have the effect of DDoS attacks, so it is recommended that you limit your tests to your own website.
Apache dates back to 1995 and early in the Internet, when an acceptable way for servers to run was to generate a new process on each incoming TCP connection and reply to it. If more connections come in, more worker processes are created to handle them. The cost of generating new processes is very high, and Apache developers designed a prefork mode where pre-makes a certain number of processes. Dynamic language interpreters embedded in each process (such as mod_php) are still costly, and Apache's default settings cause the server to crash. Each process can only process one incoming connection.
This model is called mpm_prefork_module in Apache's MPM (Multi-Process Module) system. According to Apache's website, this mode requires little configuration, as it can self-regulate, and the most importantly, the MaxRequestWorkers directive is large enough to handle as many simultaneous requests as you expect to receive, but small enough to Make sure all processes have enough physical RAM.
A small Locust load test showing a large number of Apache processes generated to handle incoming traffic.
We can add that this pattern may be the main reason for Apache's infamous reputation. It can be resource inefficient.
Version 2.0 of Apache brings two other MPMs that try to solve the prefork mode problem. They are worker module or mpm_worker_module and event module.
The Worker module is no longer process-based; it is a hybrid process-based operation mode. Quote Apache's website:
Single control process (parent process) is responsible for starting the child process. Each child process creates a fixed number of server threads based on the number specified in the ThreadsPerChild directive, and a listening thread that listens for connections and passes them to the server thread for processing when the connection arrives.
This mode saves more resources.
Apache's version 2.4 brings us the third MPM - event module. It is based on the worker MPM and adds a separate listening thread that manages the sleepy keepalive connection after the HTTP request is completed. It is a non-blocking asynchronous mode with a smaller memory footprint. More information about version 2.4 improvements are here.
We loaded a test WooCommerce installer with about 1200 posts on the virtual server and tested it on Apache 2.4 with the default prefork mode and mod_php.
First, we tested it using libapache2-mod-php7 and mmpm_prefork_module on https://tools.pingdom.com:
We then tested the event MPM module.
We have to add multiverse to our /etc/apt/sources.list:
<code>from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") @task(2) def shop(self): self.client.get("/?page_id=5") @task(3) def page(self): self.client.get("/?page_id=2") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 3000 </code>
Then we execute sudo apt-get update and install libapache2-mod-fastcgi and php-fpm:
<code>locust --host=https://my-website.com </code>
Since php-fpm is a separate service from Apache, it needs to be restarted:
<code>deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse deb http://archive.canonical.com/ubuntu xenial partner</code>
Then we disabled the prefork module and enabled event mode and proxy_fcgi:
<code>sudo apt-get install libapache2-mod-fastcgi php7.0-fpm </code>
We add this snippet to our Apache virtual host:
<code>sudo service start php7.0-fpm </code>
This port needs to be consistent with the php-fpm configuration in /etc/php/7.0/fpm/pool.d/www.conf. More information about php-fpm settings is here.
Then we adjusted the mpm_event configuration in /etc/apache2/mods-available/mpm_event.conf, remembering that the mini VPS resources we had for this test were limited - so we just reduced some default numbers. Details of each directive on the Apache website, and tips for event mpm here. Remember that the startup servers will consume a certain amount of memory, no matter how busy they are. The MaxRequestWorkers directive sets the number of simultaneous requests allowed: Setting MaxConnectionsPerChild to a non-zero value is important because it prevents possible memory leaks.
<code>from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") @task(2) def shop(self): self.client.get("/?page_id=5") @task(3) def page(self): self.client.get("/?page_id=2") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 3000 </code>
Then we use sudo service apache2 restart to restart the server (if we change some directives, such as ThreadLimit, we need to explicitly stop and start the service, using sudo service apache2 stop; sudo service apache2 start).
Our test on Pingdom now shows that the page loading time has been reduced by more than half:
Disable.htaccess: htaccess allows setting up specific configurations for each directory in the server root directory without restarting. Therefore, iterating through all directories to find the .htaccess file on each request will result in performance losses.
Quoted from Apache documentation:
Generally speaking, the .htaccess file should be used only if you do not have permission to access the primary server configuration file. … Generally speaking, .htaccess files should be avoided as much as possible. You can do whatever configuration you think you want to put in the .htaccess file as efficiently as using the
section in the main server configuration file.
The solution is to disable it in /etc/apache2/apache2.conf:
<code>locust --host=https://my-website.com </code>
If we need it for a specific directory, then we can enable it in the
<code>deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse deb http://archive.canonical.com/ubuntu xenial partner</code>
Nginx
Forking processes are very expensive compared to event loops. The event-based HTTP server ultimately wins.This statement sparked a rather intense debate on Hacker News, but in our experience, just switching from mpm_prefork Apache to Nginx usually means preventing websites from crashing. Simply switching to Nginx is usually a workaround in itself.
A more comprehensive visual explanation of the Nginx architecture can be found here.
Nginx settings
worker_connections sets the number of connections that each worker process can handle. The default is 512, but it can usually be increased.
Keepalive connection is the server aspect that affects performance, which is usually not visible in benchmarks.
According to Nginx website:
HTTP keepalive connection is a necessary performance feature that reduces latency and speeds up web page loading.
Establishing a new TCP connection can be expensive—not to mention the situation involving HTTPS encryption. The HTTP/2 protocol mitigates this with its multiplexing capabilities. Reusing existing connections can reduce request time.
Apache's mpm_prefork and mpm_worker have concurrency restrictions, which is in contrast to the keepalive event loop. This is fixed to some extent in the mpm_event module of Apache 2.4 and appears as the only default mode of operation in Nginx. Nginx worker can handle thousands of incoming connections at the same time, and if it is used as a reverse proxy or load balancer, Nginx uses a local keepalive connection pool without TCP connection overhead.
keepalive_requests is a setting that regulates the number of requests a client can make through a single keepalive connection. keepalive_timeout Sets the time when the idle keepalive connection remains open.
keepalive is a setting related to Nginx's connection to an upstream server - when it acts as a proxy or load balancer. This means the number of free keepalive upstream connections per worker process.
Enabling upstream keepalive connections requires putting these instructions into the Nginx main configuration:
<code>from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") @task(2) def shop(self): self.client.get("/?page_id=5") @task(3) def page(self): self.client.get("/?page_id=2") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 3000 </code>
Nginx upstream connections are managed by ngx_http_upstream_module.
If our front-end application keeps polling our back-end application for updates, increasing keepalive_requests and keepalive_timeout will limit the number of connections that need to be made. The keepalive directive should not be too large to allow other connections to our upstream server.
The adjustments to these settings are based on the specific situation and need to be tested. This is perhaps one of the reasons why keepalive does not have a default setting.
By default, Nginx uses a separate PHP process to forward PHP file requests. Here, it acts as a proxy (just like when we set up Apache with php7.0-fpm).
Usually, our virtual host settings using Nginx are as follows:
<code>locust --host=https://my-website.com </code>
Since FastCGI and HTTP are different protocols, the first two lines forward some parameters and headers to php-fpm, while the third line specifies how proxy requests - through local network sockets.
This is practical for multi-server setup, as we can also specify the remote server to which requests are to be proxyed.
However, if we host the entire setup on a single system, we should use a Unix socket to connect to the listening php process:
<code>deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse deb http://archive.canonical.com/ubuntu xenial partner</code>
Unix sockets are considered to have better performance than TCP, and this setting is considered safer. You can find more details about this setting in this article by Rackspace.
This tip about Unix sockets also works for Apache. More details are here.
gzip_static: The general view on web server performance is to compress our static resources. This usually means we will try to compromise and try to compress only files that exceed a certain threshold, as dynamically compressing resources on each request can be expensive. Nginx has a gzip_static directive that allows us to provide a gzip version of the file (extension .gz), instead of a regular resource:
<code>from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): @task(1) def index(self): self.client.get("/") @task(2) def shop(self): self.client.get("/?page_id=5") @task(3) def page(self): self.client.get("/?page_id=2") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 300 max_wait = 3000 </code>
This way, Nginx will try to provide style.css.gz instead of style.css (in this case we need to handle gzipping ourselves).
In this way, the CPU cycles are not wasted on dynamic compression for each request.
The story about Nginx is incomplete without mentioning how to cache content. Nginx caching is so efficient that many system administrators think that a separate HTTP cache layer (such as Varnish) doesn't make much sense. "Simplicity is a characteristic." Enabling Nginx caching is very simple.
<code>locust --host=https://my-website.com </code>
This is the directive we place in the virtual host file, located outside the server block. The proxy_cache_path parameter can be any path we want to store the cache. levels specifies how many directory levels Nginx should store cached content. For performance reasons, two levels are usually OK. It can be time-consuming to traverse the directory. The keys_zone parameter is the name of the shared memory area used to store cache keys, and 10m is the space for these keys in memory (10MB is usually enough; this is not the space for the actual cached content). max_size is optional, which sets the upper limit of cached contents - here is 10GB. If not specified, it will take up all available space. inactive specifies how long the content can stay in the cache before it is requested and then deleted by Nginx.
After setting, we will add the following line containing the name of the memory area to the server or location block:
<code>deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse deb http://archive.canonical.com/ubuntu xenial partner</code>
Additional fault-tolerant tiers of Nginx can be implemented by telling Nginx to provide items from the cache when an error occurs in the source server, upstream server, or server downtime:
<code>sudo apt-get install libapache2-mod-fastcgi php7.0-fpm </code>
For more details on the server or location block instructions to further tweak the Nginx cache, see here.
proxycache directives are used for static resources, but we usually want to cache the dynamic output of our web application—whether it is CMS or something else. In this case, we will use the fastcgicache directive instead of proxycache*:
<code>sudo service start php7.0-fpm </code>
The last line above will set the response header to tell us whether the content is passed from the cache or not.
Then, in our server or location block, we can set some cache exceptions - for example, when a query string exists in the request URL:
<code>sudo a2dismod php7.0 mpm_prefork sudo a2enmod mpm_event proxy_fcgi </code>
Also, in the case of PHP, in the .php block in the server, we will add something like the following:
<code><filesmatch> SetHandler "proxy:fcgi://127.0.0.1:9000/" </filesmatch></code>
Above, fastcgi_cache* lines and fastcgi_no_cache regulate caching and exclusion. A detailed reference to all of these instructions can be found on the Nginx documentation website.
To learn more, Nginx staff has provided a free webinar on this topic and there are a lot of e-books available.
We try to introduce some technologies that help improve web server performance and the theories behind them. But this topic is by no means exhaustive: we still don't cover reverse proxy settings or multi-server settings made up of Apache and Nginx. The best results on both servers depend on testing and analyzing specific actual cases. This is a never-ending topic.
Apache and Nginx are both powerful web servers, but there are significant differences in performance and optimization capabilities. Apache is an older server that uses a process-driven model to create a new thread for each request. This can lead to a large amount of memory usage when dealing with multiple concurrent connections. Nginx, on the other hand, uses an event-driven architecture that allows it to handle thousands of connections simultaneously, with very little memory usage. This makes Nginx more efficient and faster, especially in static content delivery and reverse proxy scenarios.
There are multiple ways to optimize Nginx for better performance. First, you can adjust the worker process and worker connection. The worker process should be set to the number of CPUs or cores, while the worker connection should be set to the maximum open file limit. Second, you can enable gzip compression to reduce the size of data Nginx sends to the client. Third, you can use cache to store frequently accessed data in memory, reducing disk I/O operations. Finally, you can use load balancing to spread network traffic across multiple servers, improving response time and overall performance.
Apache can be optimized in a variety of ways. First, you can adjust the MaxClients directive to control the maximum number of concurrent connections. Second, you can enable mod_deflate to compress data before sending it to the client, thereby reducing bandwidth usage. Third, you can use mod_cache for cache to store frequently accessed data in memory, reducing disk I/O operations. Finally, you can load balancing using mod_proxy_balancer to spread network traffic across multiple servers, improving response time and overall performance.
Yes, you can use both Apache and Nginx in your reverse proxy settings. In this configuration, Nginx acts as the front-end server that handles client requests, and Apache acts as the back-end server that handles these requests. This setup combines the advantages of both servers, Nginx efficiently handles static content, while Apache provides dynamic content processing.
Nginx excels in providing static content due to its event-driven architecture, which allows it to handle thousands of concurrent connections simultaneously with minimal memory usage. For dynamic content, Nginx can pass requests to an application server (such as PHP-FPM) or proxy them to an Apache server. However, Nginx does not handle dynamic content locally like Apache uses its mod_php module.
Apache can provide static and dynamic content. For static content, Apache uses its core modules. For dynamic content, Apache uses additional modules such as mod_php to process PHP scripts. However, Apache's process-driven model can lead to a large amount of memory usage when dealing with multiple concurrent connections, making it less efficient than Nginx in terms of static content delivery.
Server optimization can significantly improve website performance. It can reduce server response time, increase the number of concurrent connections the server can handle, and reduce bandwidth usage. This can lead to faster page load times, better user experience and improved SEO rankings.
Choosing between Apache and Nginx depends on your specific needs. If you need a server that can handle a lot of concurrent connections efficiently, or you mainly serve static content, then Nginx may be a better choice. If you need a server that has strong support for dynamic content processing, or if you rely on .htaccess files for configuration, Apache may be more suitable.
Common performance issues with Apache include high memory usage and slow response times when handling multiple concurrent connections. For Nginx, common problems include improper configuration of worker processes and connections and lack of dynamic content processing capabilities.
You can use a variety of tools to monitor the performance of Apache and Nginx. For Apache, you can use the mod_status module to provide server status information. For Nginx you can use the stub_status module. Additionally, you can use third-party monitoring tools such as New Relic, Datadog, or Nagios to get more detailed performance metrics and alerts.
The above is the detailed content of Apache vs Nginx Performance: Optimization Techniques. For more information, please follow other related articles on the PHP Chinese website!