Fork me on GitHub

Summary: heavily utilizes Nginx in both our WordPress environment and routing layer. Here’s a short description of our Nginx use


Traditionally, most hosting sites have been using Apache bundled with PHP as their web server of choice. However, during the last few years many have migrated from Apache to Nginx due to performance benefits and increased flexibility.

Default settings

Nginx comes pre-configured to use with WordPress in the Seravo setup. There is no need to peruse any guides online on how to set up Nginx with WordPress.

Our default Nginx configuration includes:

  • Strong HTTPS settings (see example)
  • A Let’s Encrypt certificate for your public domains
  • HTTP cache with stale cache configured
  • Automatic expiration headers for static content
  • The Pagespeed module
  • User configurable API
  • gzip
  • And lots more…


In most generic Apache hosting platforms, you have the ability to use .htaccess files to add custom rules to your web server. There are multiple reasons why this is not optimal, one being the inherently slower execution time for all page loads on the site.

Instead, we give our users access to their nginx configuration through a directory that contains the .conf files which will be read by Nginx on startup. These configuration files can be found in /data/wordpress/nginx/*.conf.

Note: Under the hood, your custom Nginx configuration is included like this:

server {
  listen 80 default_server;

  include /data/wordpress/nginx/*.conf;


How to create your own rules

The file /data/wordpress/nginx/examples.conf includes a few examples for you to start with, all disabled by comments. We recommend that you do not edit this file directly. Leave it as a reference instead and create your own custom.conf file from scratch.

$ cd /data/wordpress/nginx
$ nano custom.conf

To get started with your configuration you can copy stuff from examples.conf. Remove the comments from the beginning of the line and edit the rules to match the specific needs of your site.

Restarting Nginx

After you’ve made changes to your nginx configuration, please reload the configuration by running


If there should be any errors wp-restart-nginx will warn you about them and refuse to restart before the issue is fixed.



Please see our separate documentation page on Redirects. In most cases it is recommended to do the redirection and HTTP request rewrite logic in WordPress/PHP, which is much more flexible than Nginx. There are no downsides to doing redirects in WordPress/PHP as it is equally fast as soon as the first redirect has been cached.

Forcing HTTPS

To force all your users to connect via https, you may redirect them to the https side with a Nginx configuration.

Note: There are many built-in methods in WordPress to enforce HTTPS and the Seravo Plugin also enforces both https and a canonical domain when it can detect that what that those are available, so most of the time one should not use the Nginx configuration below.

The preferred method in Nginx to force HTTPS is with a custom variable.

# Force redirect http -> https
set $force_https 1;

Adding HSTS headers

When enforcing HTTPS use, it is also good to emit a HSTS header, which tells browsers that the site is supposed to only use HTTPS, and the browsers themselves will help enforce that.

The simplest form would be:

add_header Strict-Transport-Security "max-age=63072000";

The most extensive version is:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

Remember to validate with checker that the HSTS headers were applied correctly!

Note: There is no requirement to emit HSTS headers using Nginx configs. One can as well send them from the site PHP code using the header() function.

Adding CSP headers

Content Security Policy is a similar HTTP headers based system to tell browsers what kind of contents to expect from the site. The basic idea is to whitelist all possible assets and content types to the browser. If something foreign has been injected on the site (e.g. via a XSS attack), browsers would know not to load it.

Read more about CSP at the Mozilla Foundation website. The CSP syntax is quite complex and in particular on WordPress sites, where the plugins can an any given time update and start loading new assets, it is quite hard to get the CSP rules right, so using CSP is recommended only for experienced developers who can put a lot of time into deploying and monitoring it.

As a minimum, a WordPress site could have this CSP rule to enforce HTTPS use:

add_header Content-Security-Policy "upgrade-insecure-requests";

Making exceptions to the default X-Frame-Options rule

Due to security reasons, X-Frame-Options is set for all customers of Seravo by default. Customers can set their own X-Frame-Options and thus override the default values, but it cannot be removed altogether.

This can as easily be emitted from PHP code as well with a simple header("X-Frame-Options: ALLOWALL");. To have this emitted for a section of the WordPress site, the functions.php could include something like this, for example:

if ( strpos($_SERVER['REQUEST_URI'], '/widget/') !== false ) {
  header("X-Frame-Options: ALLOWALL");

If a single page or a section of the site without PHP functionality needs to have a more relaxed rule to allow arbitrary access, it can be done with a rule like this:

location /widget/ {
  add_header X-Frame-Options ALLOWALL;

Note that ALLOWALL is not an official keyword, but most browsers support it anyway. In the future the whole X-Frame-Options header will be superseded by a new system called frame ancestors. If a site emits any frame-ancestors headers, the browser will follow those rules and ignore whatever was set in the X-Frame-Options headers.

Serve two different sites from the same domain

Sometimes it makes sense to have two separate sites served from the same domain, as it will contribute to a better user experience and improve the search engine ranking for the content of the sites, when compared to having the site split to separate domains or subdomains. For example, the majority of the site could be hosted on WordPress, but the section might be on Magento.

This can be achieved using Nginx proxying. For example, create a file called /data/wordpress/nginx/store-proxy.conf with the contents:

location /store/ {
  proxy_ssl_server_name on;
  proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Protected/authenticated downloads

The path /wp-content/uploads/woocommerce_uploads/ is pre-configured by Seravo to be protected and accessible only when authentication is done in WooCommerce/WordPress/PHP while the download itself is handled by Nginx via the X-Sendfile header instructions.

One may place any file in that path and it the server will not allow a direct download of it. It can be accessed only if the PHP code emits a special X-Accel-Redirect header with the path of a file in that location.

This is the correct way to implement such a feature, since the PHP worker will not be reserved for the entire download but can continue to serve other PHP requests while the download itself has been offloaded to Nginx to handle.

To make a similar protected folder in a custom location, add the following to e.g. /data/wordpress/nginx/protected-downloads.conf:

location /protected-files {
  alias /data/wordpress/protected-files/;

In PHP emit the headers as follows:

// Do authentication etc first
if ( current_user_can( 'download_special_files' ) ) {
  // Emit header to Nginx which will send the file to requester
  header('X-Accel-Redirect: //protected-files/confidential.pdf');
} else {
  echo 'Access denied.';

Note: Do not use the secure link feature with WooCommerce. Use the built-in digital download features of WooCommerce instead, which works out-of-the-box at Seravo.

The Nginx Secure Link module can be used to create links to downloadable items (for example a PDF file) that are valid only for a short time.

Example Nginx configuration, e.g. /data/wordpress/nginx/securelink.conf:

# Make folder inaccessible publicly
location /my-restricted-files/ {
  alias /data/wordpress/my-restricted-files/;

# Define secure link
location ~ /my-restricted-files/(.*) {
    # Define values used in generating secure links
    secure_link $arg_md5, $arg_expires;

    # Define link form. Replace ASDF1234 with an unique secret
    secure_link_md5 "$secure_link_expires$uri ASDF1234";
    if ($secure_link = "") {
        return 403;
    if ($secure_link = "0") {
        return 410;

To generate links in PHP use something like:

function securelink($path) {
  $secret = 'ASDF1234'; // the same secret as in Nginx config
  $expires = time() + 86400; // 24h in seconds
  $url = md5sum("$expires/my-restricted-files/example.pdf $SECRET")

The PageSpeed module

PageSpeed sniffs through your html documents and rewrites them to be faster. This process is very site specific, so make sure that you test everything carefully before enabling PageSpeed in production. You can find more about all the filters available in Google documentation.

Turn pagespeed on

Create a new Nginx config file for PageSpeed /data/wordpress/nginx/pagespeed.conf and add the following to enable the module.

pagespeed on;

Below you can find PageSpeed filters that you can enable by appending to the created config file. After you have made changes and saved them, you should restart Nginx with wp-restart-nginx.

Add some PageSpeed filters

We have created a list of filters that might be beneficial to you in our environment. Due to PageSpeed’s benefits being really site specific, you should do own testing on what is really needed.

# Optimize resource loading
pagespeed EnableFilters rewrite_css,combine_css,inline_css,flatten_css_imports,inline_javascript,combine_javascript,inline_google_font_css,canonicalize_javascript_libraries;
  • rewrite_css minifies css by removing comments and whitespace as well as shortening some parts of the css files
  • combine_css combines site’s css files into a large common .css file in order to reduce the number of HTTP requests
  • inline_css places small external css styles directly into the html document in order to reduce the number of HTTP requests
  • flatten_css_imports replaces css @import rules with the styles defined in the imported stylesheet combining all into the same file
  • inline_javascript places small external JavaScript snippets directly into the html document in order to reduce the number of HTTP requests
  • combine_javascript combines site’s JavaScript files into a large common .js file in order to reduce the number of HTTP requests
  • inline_google_font_css on sites with Google Fonts, inlines the css directly into the page reducing the number of HTTP requests
  • canonicalize_javascript_libraries fetches some popular JavaScript libraries from Google Hosted Libraries and speedifies webpage load time if the libraries were fetched from a slower external server before

Note that with the introduction of HTTP/2, many of the techniques used by PageSpeed have become obsolete. Currently very few of our customers use PageSpeed because of the limited benefits it brings but also due to the alternative site optimization methods we provide. For example, the image optimization filters provided in PageSpeed can be replaced by Seravo Plugin and using an alternative way to show WebP images.