Nginx is a HTTP server software with focus on core web server and proxy features. It is very common because of its resource efficiency and responsiveness under load. Nginx spawns worker processes, each of which can handle thousands of connections. Each of the connections handled by the worker get placed within an event loop where they exist with other connections. Within the loop, events get processed asynchronously, allowing work to be handled in a non-blocking manner. When the connection closes, it gets removed from the loop. This style of connection processing allows Nginx to scale incredibly far with limited resources.


This page explains how to run Grav with Nginx as the HTTP server and PHP-FPM (FastCGI Process Manager) to process PHP scripts, so these packages need to be installed on your server:

  • nginx
  • php-fpm


If you are new to Nginx and don't yet have a basic understanding of block directives/context, it is recommended to read the Nginx Beginners's Guide, especially the sections Configuration File’s Structure and Serving Static Content.

It is assumed that your Nginx configuration is located in /etc/nginx/ and your Grav installation is stored in /var/www/grav/. The structure of the configuration is a http block that contains general directives relevant for all pages served by Nginx, as well as one or multiple server blocks for each page, containing site-specific directives. The main server configuration file is nginx.conf and stores the http block, while site-specific configurations (server blocks) are stored in sites-available and symlinked to sites-enabled.

File Permissions

The /var/www directory and all contained files and folders should be owned by $USER:www-data (or whatever you name the Nginx user/group). The section Troubleshooting/Permissions explains how to setup file and directory permissions for Grav, in this case using a shared group. Basically what you want is 775 for directories and 664 for files in the Grav directory, so Grav is allowed to modify content and upgrade itself. You should add your user to the www-data group so you can access files that are created by Grav/Nginx.

Example nginx.conf

The following configuration is an improved version of the default /etc/nginx/nginx.conf file, mainly with improvements from See their repository for explanations on these settings or the Nginx core module and http module documentation to look up specific directives.

It is recommended to use an updated MIME types definition file (mime.types) from This will make sure that the types are correctly set for gzip compression.


user www-data;
worker_processes auto;
worker_rlimit_nofile 8192; # should be bigger than worker_connections
pid /run/;

events {
    use epoll;
    worker_connections 8000;
    multi_accept on;

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 30; # longer values are better for each ssl client, but take up a worker connection longer
    types_hash_max_size 2048;
    server_tokens off;

    # maximum file upload size
    # update 'upload_max_filesize' & 'post_max_size' in /etc/php/fpm/php.ini accordingly
    client_max_body_size 32m;
    # client_body_timeout 60s; # increase for very long file uploads

    # set default index file (can be overwritten for each site individually)
    index index.html;

    # load MIME types
    include mime.types; # get this file from
    default_type application/octet-stream; # set default MIME type

    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # turn on gzip compression
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_min_length 256;

    # disable content type sniffing for more security
    add_header "X-Content-Type-Options" "nosniff";

    # force the latest IE version
    add_header "X-UA-Compatible" "IE=Edge";

    # enable anti-cross-site scripting filter built into IE 8+
    add_header "X-XSS-Protection" "1; mode=block";

    # include virtual host configs
    include sites-enabled/*;

Grav Site Configuration

Grav gets shipped with a configuration file for your site in the webserver-configs directory of your Grav installation. You can copy that file into your nginx config directory:

cp /var/www/grav/webserver-configs/nginx.conf /etc/nginx/sites-available/grav-site

Open that file with an editor and replace "" with your domain/IP (or "localhost" if you want to just run it locally), replace the "root" line with "root /var/www/grav/;" and then create a symbolic link of your site-config in sites-enabled:

ln -s /etc/nginx/sites-available/grav-site /etc/nginx/sites-enabled/grav-site

Finally let Nginx reload its configuration:

nginx -s reload

Fix against httpoxy vulnerability

httpoxy is a set of vulnerabilities that affect application code running in CGI, or CGI-like environments. (Source:

In order to secure your site against this vulnerability you should block the Proxy header. This can be done by adding a FastCGI parameter to your config. Simply open the file /etc/nginx/fastcgi.conf and add this line at the end:

fastcgi_param  HTTP_PROXY         "";

Using SSL (with an existing certificate)

If you want to use an existing SSL certificate to encrypt your website traffic, this section provides the necessary steps to modify your Nginx configuration for that.

First, create a file /etc/nginx/ssl.conf with the following content and adjust the paths to your certificate and key file. The last section is about OSCP stapling and requires you to provide a chain+root certificate. If you don't want this, you can comment or remove everything below the OCSP Stapling comment. If your website is SSL only (including subdomains), you can submit your domain for preloading in browsers at If that isn't the case, you can remove preload; from the line that adds the "Strict-Transport-Security" header. Make sure to check if all of these options work for your setup.


# set the paths to your cert and key files here
ssl_certificate /etc/ssl/certs/;
ssl_certificate_key /etc/ssl/private/;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_prefer_server_ciphers on;

ssl_session_cache shared:SSL:10m; # a 1mb cache can hold about 4000 sessions, so we can hold 40000 sessions
ssl_session_timeout 24h;

# Use a higher keepalive timeout to reduce the need for repeated handshakes
keepalive_timeout 300s; # up from 75 secs default

# submit domain for preloading in browsers at:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload;";

# OCSP stapling
# nginx will poll the CA for signed OCSP responses, and send them to clients so clients don't make their own OCSP calls.
# see on how to create the chain+root
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/;
resolver valid=60s;
resolver_timeout 2s;

Now change the content of your Grav-specific config /etc/nginx/sites-available/grav-site to redirect unencrypted HTTP requests to HTTPS, that means to a server block listening on port 443 and including your ssl.conf (replace "" with your domain/IP). You can also change this to redirect from the non-www to the www version of your domain.


# redirect http to non-www https
server {
    listen [::]:80;
    listen 80;

    return 302$request_uri;

# redirect www https to non-www https
server {
    listen [::]:443 ssl;
    listen 443 ssl;

    # add ssl cert & options
    include ssl.conf;

    return 302$request_uri;

# serve website
server {
    listen [::]:443 ssl;
    listen 443 ssl;

    # add ssl cert & options
    include ssl.conf;

    root /var/www/;

    index index.html index.php;

    # ...
    # the rest of this server block (location directives) is identical to the one from the shipped config

Finally reload your Nginx configuration:

nginx -s reload

Nginx Cache headers for assets

It is also recommended to enable those in production. These additions to the configuration file will handle them. 'expires' defines the expiration time for the cache, 30 days in this case. Please see the full documentation about http headers for nginx here

location ~* ^/forms-basic-captcha-image.jpg$ {
                try_files $uri $uri/ /index.php$is_args$args;

        location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
                expires 30d;
                add_header Vary Accept-Encoding;
                log_not_found off;

        location ~* ^.+\.(?:css|cur|js|jpe?g|gif|htc|ico|png|html|xml|otf|ttf|eot|woff|woff2|svg)$ {
                access_log off;
                expires 30d;
                add_header Cache-Control public;

## No need to bleed constant updates. Send the all shebang in one
## fell swoop.
                tcp_nodelay off;

## Set the OS file cache.
                open_file_cache max=3000 inactive=120s;
                open_file_cache_valid 45s;
                open_file_cache_min_uses 2;
                open_file_cache_errors off;

Found errors? Think you can improve this documentation? Simply click the Edit link at the top of the page, and then the icon on Github to make your changes.