Should I use Nginx vs Apache?

Choosing a web server

< Blog

Should I use Nginx vs Apache? Screenshot
July 30, 2019 | XEO

"I didn't switch to Nginx until I understood how it handled virtual hosts" - Xeo

What are Nginx and Apache?

Both Nginx and Apache are web servers which host your website on the server. They can also serve API and mobile applications. Nginx goes a little further and can be a proxy, in fact it is used in CloudFlare and provides some of the clever features. Fundamentally a web server reads the web site code from the disk and returns it to the web browser, potentially caching along the way. Each subdomain.domain.com becomes its own Virtual Host and has a separate configurations; basically which folder to use for which domain.

Behind the web server is a multi-threaded model. There are several to choose from. PHP-FPM is the one XeoDev uses because it relies on pipes for optimal internal communications and it is configurable based on web server capacity. Matching the multi-threaded model with the web server provides the optimal result.

So how much faster is Nginx?

At a high level we are seeing about 1 full second peformance improvements on each page load with Nginx + PHP-FPM over Apache with the default model. In addition, this performance was maintained well as more clients were added. But this is not a performance measurement blog. What this told us was we should make the switch.

How well does Nginx Work with Webpack?

We upgraded to webpack, using Laravel Mix, at the same time as we changed from Apache to Nginx. We saw significant wins from each upgrade. Webpack requires a build step during the deployment process and we had to upgrade several places in the JS layer where we were not declaring all variables. There were also some load order conditions to overcome which caused us to split out vendor.js from core.js to give the loading some flexibility. But those were solid refactoring improvements and the drop from so many individual files to 3 JS and 3 CSS files which are cached 99% of the time and then a handful of area specific JS files at one or two per page is much cleaner in the browser.

XeoDev Standard Nginx Installation

Note: this installation continues to have user apache be the web server user so it changes Nginx to apache in a few places
          
          ### install Nginx
          sudo yum install Nginx -y
          sudo chkconfig Nginx on
          sudo service Nginx start

          ### install php71-fpm
          sudo yum install php71-fpm
          sudo chkconfig php-fpm on
          sudo service php-fpm start

          ### Nginx config
              cd /etc/Nginx
              sudo vi Nginx.conf
              --
              user apache;
              --

          ### configure fpm
              sudo vi /etc/php-fpm.d/www.conf

              Note: to fix ACL issue, comment out listen.acl_users = apache,Nginx
              --
              [global]
              emergency_restart_threshold = 10
              emergency_restart_interval = 1m
              process_control_timeout = 10s

              [www]
              listen = /var/run/php-fpm/www.sock
              listen.owner = apache
              listen.group = apache
              listen.mode = 0664
              user = apache
              group = apache

              ;listen.acl_users = apache

              pm.max_children = 20
              pm.start_servers = 5
              pm.min_spare_servers = 5
              pm.max_spare_servers = 20
              pm.max_requests = 200

              php_admin_value[memory_limit] = 128M
              --

              sudo service php-fpm restart
          
          

XeoDev Standard Nginx Configuration

I do love Stack Overflow. But it frequently requires reading a dozen decent articles and harvesting answers that are not the checked answers to get a complete solution. Especially a complete solution for the current year, which is 2019. So we decided to publish the configuration we are using. Feel free to give us feedback using the contact us page (don't have reply working in this static blog).

The configuration design is to use a standards.inc, a fastcgi.inc and a series of *vhost*.conf files to handle each vhost. This way the vhost files are less than one page and include the other two files making global changes require changing only the .inc file. Some of the lines are commented out as we are still experimenting with them.

Standards.inc

          
          #
          # Standard stuff for all virtual hosts
          #

          # SSL config

              # enable session resumption to improve https performance
              ssl_session_timeout 1d;
              ssl_session_cache shared:SSL:50m;
              ssl_session_tickets off;

          # limit SSL ciphers
              ssl_protocols TLSv1.2 TLSv1.3;
              ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256";
              ssl_prefer_server_ciphers on;
              ssl_ecdh_curve secp384r1;

          # DNS resolver choice

              # resolver 8.8.8.8;

          # OCSP Stapling ---

              # fetch OCSP records from URL in ssl_certificate and cache them
              # ssl_stapling on;
              # ssl_stapling_verify on;

          # security headers

              # http security headers
                  add_header X-Content-Type-Options nosniff;
                  add_header X-Frame-Options DENY;
                  #add_header Pragma no-cache;
                  #add_header Cache-Control no-store;
                  add_header X-XSS-Protection "1; mode=block";
                  add_header Referrer-Policy origin-when-cross-origin;
                  add_header X-Permitted-Cross-Domain-Policies none;
                  add_header Strict-Transport-Security "max-age=100000" always;

              # nonce!!, upgrade-insecure-requests!!
                  add_header Content-Security-Policy "upgrade-insecure-requests";
                  #add_header Content-Security-Policy "upgrade-insecure-requests; default-src 'self'; base-uri 'self'; require-sri-for script; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-JjECqn6A' http: https:; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; media-src 'none'; frame-src 'self'; font-src 'self'; connect-src 'self' wss:; report-uri https://yourdomain.report-uri.com/r/d/csp/enforce;";

              # Add Security cookie flags
                  #proxy_cookie_path ~(.*) "$1; SameSite=strict; secure; httponly";

          # gzip
              gzip on;
              gzip_vary on;
              gzip_comp_level 5;
              gzip_min_length 10240;
              gzip_proxied any;
              gzip_types
              application/atom+xml
              application/javascript
              application/json
              application/ld+json
              application/manifest+json
              application/rss+xml
              application/vnd.geo+json
              application/vnd.ms-fontobject
              application/x-font-ttf
              application/x-web-app-manifest+json
              application/xhtml+xml
              application/xml
              font/opentype
              image/bmp
              image/svg+xml
              image/x-icon
              text/cache-manifest
              text/css
              text/plain
              text/vcard
              text/vnd.rim.location.xloc
              text/vtt
              text/x-component
              text/x-cross-domain-policy;
              gzip_disable "MSIE [1-6]\.";

          # configure expires on headers

              location ~*  \.(jpg|jpeg|png|gif|ico|css|js|pdf)$ {
                  expires 7d;
              }

          # Deny all attempts to access hidden files

              # such as .htaccess, .htpasswd, .DS_Store (Mac), .git, .etc...
              location ~ /\. {
                  deny all;
              }

          # Security Settings

              ## Start: Size Limits & Buffer Overflows ##
              #client_body_buffer_size 1K;
              #client_header_buffer_size 1k;
              #client_max_body_size 1k;
              #large_client_header_buffers 2 1k;
              ## End: Size Limits & Buffer Overflows ##

              ## - OR - ##

              ## Start: Allow 25 mb uploads ##
              #client_body_buffer_size 1K;
              #client_header_buffer_size 1k;
              client_max_body_size 25m;
              #large_client_header_buffers 2 1k;
              ## End: Allow 25 mb uploads ##

              ## Start: Timeouts ##
              client_body_timeout 10;
              client_header_timeout 10;
              keepalive_timeout 5 5;
              send_timeout 10;
              ## End: Timeouts ##

              ### Directive describes the zone, in which the session states are stored i.e. store in slimits. ###
              ### 1m can handle 32000 sessions with 32 bytes/session, set to 5m x 32000 session ###
              #limit_zone slimits $binary_remote_addr 5m;

              ### Control maximum number of simultaneous connections for one session i.e. ###
              ### restricts the amount of connections from a single ip address ###
              #limit_conn slimits 5;

          # Block robots by user agent

              ## Block some robots ##
              if ($http_user_agent ~* msnbot|scrapbot) {
                  return 403;
              }


          
          

Fastcgi.inc

          
          fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
          fastcgi_param  QUERY_STRING       $query_string;
          fastcgi_param  REQUEST_METHOD     $request_method;
          fastcgi_param  CONTENT_TYPE       $content_type;
          fastcgi_param  CONTENT_LENGTH     $content_length;
          fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
          fastcgi_param  REQUEST_URI        $request_uri;
          fastcgi_param  DOCUMENT_URI       $document_uri;
          fastcgi_param  DOCUMENT_ROOT      $document_root;
          fastcgi_param  SERVER_PROTOCOL    $server_protocol;
          fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
          fastcgi_param  SERVER_SOFTWARE    Nginx/$Nginx_version;
          fastcgi_param  REMOTE_ADDR        $remote_addr;
          fastcgi_param  REMOTE_PORT        $remote_port;
          fastcgi_param  SERVER_ADDR        $server_addr;
          fastcgi_param  SERVER_PORT        $server_port;
          fastcgi_param  SERVER_NAME        $server_name;

          fastcgi_index  index.php;

          fastcgi_param  REDIRECT_STATUS    200;
          
          

{vhost}.conf

          
          #
          # A virtual host using mix of IP-, name-, and port-based configuration
          #

          server {
              listen   80;
              listen   [::]:80;
              server_name sub.domain.com;

              return 301 https://$host$request_uri;
          }

          server {
              listen   443 ssl http2;
              listen   [::]:443 ssl http2;

              access_log   /var/log/httpd/access-sub-domain.log;
              error_log    /var/log/httpd/error-sub-domain.log error;

              server_name  next.collegefitfinder.com;
              root         /var/www/vhosts/next-collegefitfinder/public;
              index        index.php;

              ssl on;
              ssl_certificate /etc/pki/tls/certs/domain.crt;
              ssl_certificate_key /etc/pki/tls/private/domain.key;

              include conf.d/standards.inc;

              # password protect this staging server
              auth_basic           "Secure Area";
              auth_basic_user_file conf.d/htpasswd_domain.com;

              location / {
                  root         /var/www/vhosts/sub-domain/public;
                  try_files $uri $uri/ /index.php?$query_string;
              }

              location ~ \.php$ {
                  fastcgi_pass   unix:/var/run/php-fpm/www.sock;
                  fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                  include        conf.d/fastcgi.inc;
              }
          }
          
          

Other Config Changes

Made these permission changes because i'm running with the apache user since this way we can switch back and forth to apache.
          
          sudo chown apache:root /var/lib/Nginx
          sudo chown apache:root /var/lib/Nginx/tmp
          
          


About XeoDev

During the post mortem meeting on the release one of the client partners raise this high compliment for the XeoDev co-founders: 'David contributed as if he were a founder of our company, not as a vendor; and I really respect that.' That sums up our intended relationship model: we would like to be your technology partner and bring that founder mindset into your business to deliver complex redesign projects together. Xeodev provides SaaS and Mobile Android and iOS App platforms for companies ranging from solopreneurs to unicorns. When you need this type of technology for your business get started by requesting a quote.