<p>Definitely not an nginx expert, but this has been working well for anyone that's tried it so far:</p>

<pre><code>server {
  listen 8080 default;
  charset utf8;
  server_name example.com;
  root /path/to/example.com/public;
  index index.php index.html;

  # Logs
  error_log /usr/local/etc/nginx/logs/example.com.error.log debug;
  access_log /usr/local/etc/nginx/logs/example.com.access.log;

  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  location ~ ^(.*)$ {
      try_files $uri $uri/ /index.php?p=$uri&amp;$args;
  }

  location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
  }

  location = /robots.txt  { access_log off; log_not_found off; }
  location = /favicon.ico { access_log off; log_not_found off; }
  location ~ /\. { access_log off; log_not_found off; deny all; }
}
</code></pre>

<p>Note that it's making a few assumptions about how you've setup php-fpm and what ports things are listening on, so those may need to be adjusted.</p>

CC BY-SA 3.0
2014-09-10T21:50:57.177Z
Any idea how you would add cross-domain headers? When I add

`location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { add_header "Access-Control-Allow-Origin" "*"; }`
I get 404s on the fonts in the admin.
Trevor Davis
CC BY-SA 3.0
2015-02-21T21:44:00.840Z
@TrevorDavis you need to add try_files directive i.e. `location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { try_files $uri /index.php?p=$uri&$args; add_header "Access-Control-Allow-Origin" "*"; }`
mohd4482
CC BY-SA 3.0
2016-03-23T07:16:18.320Z
Please don't do this.  Adding `try_files` inside of that location block means that it's going to fire up php, Yii, and Craft when serving any of those matching files, both on the backend and on the front-end.  This is going to be very slow and inefficient way to serve static resources.  Instead make a location block for the `/admin` url to route only AdminCP requests through `index.php`
andrew.welch
CC BY-SA 3.0
2016-04-11T18:59:48.983Z
@khalwat: not true.  ``try_files`` actually makes sure the file doesn't already exist on the server first. In fact, most nginx configs for ``/`` start with try_files. Your suggestion would work as well but it's going to be the same outcome: internally since the file doesn't exist, nginx is going eventually route back to the ``php`` location block.
RitterKnight
CC BY-SA 3.0
2016-04-25T14:46:47.533Z
@RitterKnight I thought so too... however that isn't what ended up happening.  It may depend on your exact Nginx config, but in the case I mentioned, the requests for static resources were indeed being routed through index.php
andrew.welch
CC BY-SA 3.0
2016-04-25T15:18:33.333Z
@khawlat: Yes try reworking your config. It's probably a case of nginx not serving out of the ``location`` block you think that it is. ``try_files`` always looks for static files first then falls back.  On the admin side, all requests DO actually  get routed back to PHP. That's just how Craft 2 works since those static resources live behind the document root. I haven't looked at Craft's code but there's probably a way to alias them so nginx serves them directly. Craft 3 is going to change that...
RitterKnight
CC BY-SA 3.0
2014-07-11T21:41:35.470Z
2014-07-11T21:41:35.470Z
Brad Bell
<p>craftcms.conf</p>

<pre><code>root /srv/http/current-release/src/public;

index index.php index.html index.htm;

charset utf-8;

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

error_page 404 /index.php;

location / {
    # try_files $uri $uri/ /index.php?$query_string;
    try_files $uri $uri/ @rewrites;
}

location @rewrites {
    rewrite ^(.*) /index.php?p=$1 last;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

    # With php5-cgi alone:
    # fastcgi_pass 127.0.0.1:9000;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

# Expire rules for static content

# No default expire rule. This config mirrors that of apache as outlined in the
# html5-boilerplate .htaccess file. However, nginx applies rules by location,
# the apache rules are defined by type. A consequence of this difference is that
# if you use no file extension in the url and serve html, with apache you get an
# expire time of 0s, with nginx you'd get an expire header of one month in the
# future (if the default expire rule is 1 month). Therefore, do not use a
# default expire rule with nginx unless your site is completely static

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
    try_files $uri /index.php?$query_string;
    expires -1;
}

# Feed
location ~* \.(?:rss|atom)$ {
    try_files $uri /index.php?$query_string;
    expires 1h;
    add_header Cache-Control "public";
}

# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|pdf|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
    try_files $uri /index.php?$query_string;
    expires 1M;
    access_log off;
    add_header Cache-Control "public";
}

# CSS and Javascript
location ~* \.(?:css|js)$ {
    try_files $uri /index.php?$query_string;
    expires 1y;
    access_log off;
    add_header Cache-Control "public";
}

# Favicons
location = /favicon.ico {
    access_log off;
    log_not_found off;
}
# Robots.txt
location = /robots.txt {
    access_log off;
    log_not_found off;
}

# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
location ~* (?:^|/)\. {
    deny all;
}
# Prevent clients from accessing to backup/config/source files
location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
    deny all;
}
</code></pre>

<p>sites-available.conf</p>

<pre><code># http://example.com
# http://www.example.com
# This server redirects users to the authoritive non-WWW https site.
server {
    # don't forget to tell on which port this server listens
    #listen [::]:80;
    listen 80;
    server_name example.com www.example.com;
    # and redirect to the non-www host (declared below)
    return 301 https://example.com$request_uri;
}

# https://www.example.com
# This server redirects users to the secure non-WWW https site.
server {
    listen 443 ssl spdy;
    # listen on the www host
    server_name www.example.com;
    include ssl.conf;
    # and redirect to the non-www host (declared below)
    return 301 https://example.com$request_uri;
}

# http://&lt;IP address&gt;
# Allows HTTP direct to the IP address of the EC2 node
server {
    # don't forget to tell on which port this server listens
    listen 80 default_server;
    # listen on the domain name only
    server_name example.com;
    include craftcms.conf;
}

# https://example.com
# The BE ALL END ALL OF NGINX CONFIGURATIONS AT example.com
server {
    # don't forget to tell on which port this server listens
    listen 443 default_server ssl spdy;
    # listen on the domain name only
    server_name naralogics.com;
    include ssl.conf;
    include craftcms.conf;
}
</code></pre>

CC BY-SA 3.0
2016-02-12T20:39:36.300Z
Not sure, but it might be helpful if we could see an example of your ssl.conf – thanks.
kerns
CC BY-SA 3.0
2016-10-17T18:18:06.930Z
only `charset` isnt valid there
Adeerlike
CC BY-SA 4.0
2019-07-25T12:24:01.023Z
This helped me:`location / {
    # try_files $uri $uri/ /index.php?$query_string;
    try_files $uri $uri/ @rewrites;
}

location @rewrites {
    rewrite ^(.*) /index.php?p=$1 last;
}`
Chuguniy
CC BY-SA 3.0
2015-04-01T18:50:04.273Z
2015-05-18T15:16:40.203Z
2015-05-18T15:16:40.203Z
0xADADA
<p>I see this is 2 years old post but just wanted to share my findings. So, if anyone comes for a solution next time, he will be able to find a better solution. Please use this <a href="https://gist.github.com/williamgomes/5749c8f54936823af15c9f1a45713861" rel="nofollow">gist</a> created by me for details configuration.</p>

<p>Thnx.</p>

CC BY-SA 3.0
2016-08-18T15:53:41.377Z
Why not post the contents of the gist directly into your answer? This is basically a "link-only" answer, which is frowned upon.
Lindsey D
CC BY-SA 3.0
2016-08-18T15:59:25.997Z
Well as you can see, whole code will be much bigger and non-friendly. Thats why thought maybe link will serve better than whole code.
William Francis Gomes
CC BY-SA 3.0
2016-08-18T16:02:02.900Z
It may be a large answer, but it would be a comprehensive (and better) answer. As it currently stands, if you hypothetically removed the link, this isn't an answer at all. (Thus it's a "link-only" answer.)
Lindsey D
CC BY-SA 3.0
2016-08-18T11:35:38.190Z
2016-08-18T11:35:38.190Z
William Francis Gomes
<p>This Nginx-Craft configuration is one I've been using in production for some time, and it implements many best-practices that you might want to be using:</p>

<p><a href="https://github.com/nystudio107/nginx-craft" rel="noreferrer">https://github.com/nystudio107/nginx-craft</a></p>

CC BY-SA 3.0
2016-11-01T19:01:13.370Z
2016-11-01T19:01:13.370Z
andrew.welch
<p>I'm looking for the recommended way of how to configure Nginx server to run Craft.</p>

<p>Currently, I use the following configuration, it works, but I don't know if it could be better:</p>

<pre><code>server {
    listen 80;
    server_name example.com;
    root /home/vagrant/craft/public;

    index index.html index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/example.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~* (?:^|/)\. {
        deny all;
    }

    location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
        deny all;
    }

    location ~* \.(?:manifest|appcache|html?|xml|json)$ {
        try_files $uri /index.php?$query_string;
        expires -1;
        access_log /var/log/nginx/example.com-access.log;
    }

    location ~* \.(?:rss|atom)$ {
        try_files $uri /index.php?$query_string;
        expires 1h;
        add_header Cache-Control "public";
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }

    location ~* \.(?:css|js)$ {
        try_files $uri /index.php?$query_string;
        expires 1y;
        add_header Cache-Control "public";
    }

    location ~* \.(?:ttf|ttc|otf|eot|woff)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }
}
</code></pre>

<server>
What is the recommended Nginx configuration for Craft?
# What is the recommended Nginx configuration for Craft?

(Asked by: username_0 on 2014-07-11)

I'm looking for the recommended way of how to configure Nginx server to run Craft.
Currently, I use the following configuration, it works, but I don't know if it could be better:
server {
    listen 80;
    server_name example.com;
    root /home/vagrant/craft/public;

    index index.html index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/example.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~* (?:^|/)\. {
        deny all;
    }

    location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
        deny all;
    }

    location ~* \.(?:manifest|appcache|html?|xml|json)$ {
        try_files $uri /index.php?$query_string;
        expires -1;
        access_log /var/log/nginx/example.com-access.log;
    }

    location ~* \.(?:rss|atom)$ {
        try_files $uri /index.php?$query_string;
        expires 1h;
        add_header Cache-Control "public";
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }

    location ~* \.(?:css|js)$ {
        try_files $uri /index.php?$query_string;
        expires 1y;
        add_header Cache-Control "public";
    }

    location ~* \.(?:ttf|ttc|otf|eot|woff)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }
}

**Tags:** server

---

## 4 answers

### Answer by username_3 on 2016-11-01. Score: 6 | Accepted answer

This Nginx-Craft configuration is one I've been using in production for some time, and it implements many best-practices that you might want to be using:

<https://github.com/nystudio107/nginx-craft>


---

### Answer by username_1 on 2014-07-11. Score: 11

Definitely not an nginx expert, but this has been working well for anyone that's tried it so far:
server {
  listen 8080 default;
  charset utf8;
  server_name example.com;
  root /path/to/example.com/public;
  index index.php index.html;

  # Logs
  error_log /usr/local/etc/nginx/logs/example.com.error.log debug;
  access_log /usr/local/etc/nginx/logs/example.com.access.log;

  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  location ~ ^(.*)$ {
      try_files $uri $uri/ /index.php?p=$uri&$args;
  }

  location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
  }

  location = /robots.txt  { access_log off; log_not_found off; }
  location = /favicon.ico { access_log off; log_not_found off; }
  location ~ /\. { access_log off; log_not_found off; deny all; }
}

Note that it's making a few assumptions about how you've setup php-fpm and what ports things are listening on, so those may need to be adjusted.

> **Comments:**
> **username_2**: Any idea how you would add cross-domain headers? When I add  `location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { add_header "Access-Control-Allow-Origin" "*"; }` I get 404s on the fonts in the admin.  
> **username_0**: @TrevorDavis you need to add try_files directive i.e. `location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { try_files $uri /index.php?p=$uri&$args; add_header "Access-Control-Allow-Origin" "*"; }`  
> **username_3**: Please don't do this.  Adding `try_files` inside of that location block means that it's going to fire up php, Yii, and Craft when serving any of those matching files, both on the backend and on the front-end.  This is going to be very slow and inefficient way to serve static resources.  Instead make a location block for the `/admin` url to route only AdminCP requests through `index.php`  
> **username_4**: @khalwat: not true.  ``try_files`` actually makes sure the file doesn't already exist on the server first. In fact, most nginx configs for ``/`` start with try_files. Your suggestion would work as well but it's going to be the same outcome: internally since the file doesn't exist, nginx is going eventually route back to the ``php`` location block.  
> **username_3**: @RitterKnight I thought so too... however that isn't what ended up happening.  It may depend on your exact Nginx config, but in the case I mentioned, the requests for static resources were indeed being routed through index.php  
> **username_4**: @khawlat: Yes try reworking your config. It's probably a case of nginx not serving out of the ``location`` block you think that it is. ``try_files`` always looks for static files first then falls back.  On the admin side, all requests DO actually  get routed back to PHP. That's just how Craft 2 works since those static resources live behind the document root. I haven't looked at Craft's code but there's probably a way to alias them so nginx serves them directly. Craft 3 is going to change that...  

---

### Answer by username_5 on 2015-04-01. Score: 5

craftcms.conf
root /srv/http/current-release/src/public;

index index.php index.html index.htm;

charset utf-8;

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

error_page 404 /index.php;

location / {
    # try_files $uri $uri/ /index.php?$query_string;
    try_files $uri $uri/ @rewrites;
}

location @rewrites {
    rewrite ^(.*) /index.php?p=$1 last;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

    # With php5-cgi alone:
    # fastcgi_pass 127.0.0.1:9000;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
}

# Expire rules for static content

# No default expire rule. This config mirrors that of apache as outlined in the
# html5-boilerplate .htaccess file. However, nginx applies rules by location,
# the apache rules are defined by type. A consequence of this difference is that
# if you use no file extension in the url and serve html, with apache you get an
# expire time of 0s, with nginx you'd get an expire header of one month in the
# future (if the default expire rule is 1 month). Therefore, do not use a
# default expire rule with nginx unless your site is completely static

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
    try_files $uri /index.php?$query_string;
    expires -1;
}

# Feed
location ~* \.(?:rss|atom)$ {
    try_files $uri /index.php?$query_string;
    expires 1h;
    add_header Cache-Control "public";
}

# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|pdf|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
    try_files $uri /index.php?$query_string;
    expires 1M;
    access_log off;
    add_header Cache-Control "public";
}

# CSS and Javascript
location ~* \.(?:css|js)$ {
    try_files $uri /index.php?$query_string;
    expires 1y;
    access_log off;
    add_header Cache-Control "public";
}

# Favicons
location = /favicon.ico {
    access_log off;
    log_not_found off;
}
# Robots.txt
location = /robots.txt {
    access_log off;
    log_not_found off;
}

# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
location ~* (?:^|/)\. {
    deny all;
}
# Prevent clients from accessing to backup/config/source files
location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
    deny all;
}

sites-available.conf
# http://example.com
# http://www.example.com
# This server redirects users to the authoritive non-WWW https site.
server {
    # don't forget to tell on which port this server listens
    #listen [::]:80;
    listen 80;
    server_name example.com www.example.com;
    # and redirect to the non-www host (declared below)
    return 301 https://example.com$request_uri;
}

# https://www.example.com
# This server redirects users to the secure non-WWW https site.
server {
    listen 443 ssl spdy;
    # listen on the www host
    server_name www.example.com;
    include ssl.conf;
    # and redirect to the non-www host (declared below)
    return 301 https://example.com$request_uri;
}

# http://
# Allows HTTP direct to the IP address of the EC2 node
server {
    # don't forget to tell on which port this server listens
    listen 80 default_server;
    # listen on the domain name only
    server_name example.com;
    include craftcms.conf;
}

# https://example.com
# The BE ALL END ALL OF NGINX CONFIGURATIONS AT example.com
server {
    # don't forget to tell on which port this server listens
    listen 443 default_server ssl spdy;
    # listen on the domain name only
    server_name naralogics.com;
    include ssl.conf;
    include craftcms.conf;
}

> **Comments:**
> **username_6**: Not sure, but it might be helpful if we could see an example of your ssl.conf – thanks.  
> **username_7**: only `charset` isnt valid there  
> **username_8**: This helped me:`location / {     # try_files $uri $uri/ /index.php?$query_string;     try_files $uri $uri/ @rewrites; }  location @rewrites {     rewrite ^(.*) /index.php?p=$1 last; }`  

---

### Answer by username_9 on 2016-08-18. Score: 0

I see this is 2 years old post but just wanted to share my findings. So, if anyone comes for a solution next time, he will be able to find a better solution. Please use this [gist](https://gist.github.com/williamgomes/5749c8f54936823af15c9f1a45713861) created by me for details configuration.

Thnx.

> **Comments:**
> **username_10**: Why not post the contents of the gist directly into your answer? This is basically a "link-only" answer, which is frowned upon.  
> **username_9**: Well as you can see, whole code will be much bigger and non-friendly. Thats why thought maybe link will serve better than whole code.  
> **username_10**: It may be a large answer, but it would be a comprehensive (and better) answer. As it currently stands, if you hypothetically removed the link, this isn't an answer at all. (Thus it's a "link-only" answer.)  

---

