Nginx security

Events happening in the community are now at Drupal community events on www.drupal.org.
superfedya's picture

Hi,

I just put a phpinfo.php file to the sites/mysite/files/imagedirectory and was able to run it. I think is not too secure...

Any way to protect a specific directory from running a php files? Or disable to run any .php to the files folder and all sub-folders?

What exactly must be protected (maybe boost directory)? Or just /files and /tmp?

Thanks

Comments

You can deny execution of

brianmercer's picture

You can deny execution of certain files from certain directories, but most of us do it the other way around and deny all by default and then only allow certain files like index.php.

You might want to look at perusio's config.

Or post your whole config if you want.

Like Brian said

perusio's picture

the preferred way is to enumerate all files that you want to have executed and use as specific as possible locations. Usually only index.php is required. If you use other modules that require to execute some script, like the ad module, just add a location for it. Always have at the end of your config a regex based location that matches all files ended with php extension.

Note that since php-fpm 5.3.10, IIRC, only files with php extension can be processed. Thus providing an additional layer of protection besides your Nginx config.

Thanks, i'll try to do

superfedya's picture

Thanks, i'll try to do that.

There is any good manual how to setup iptables? Installed CSF, but all ports for my server ip and localhost are opened by default... Any way to block it and allow only some ports even for the server IP and even 127.0.0.1?

I used perusio's config for drupal 6. But the things like a "if ($bad_bot)" or "if ($bad_referer)" doesn't work :(

I found in blacklins.conf:

Add here all user agents that are to be blocked.

map $http_user_agent $bad_bot {
default 0;
libwww-perl 1;
~(?i)(httrack|htmlparser|libwww) 1;
}

I must install libwww-perl?

One more question, can somebody show me please an exemple of rewrite to redirect from a folder to the root? I want to migrate my site from /mysite to /

Thanks!

If you're on Ubuntu you can

brianmercer's picture

If you're on Ubuntu you can use ufw. Really simple and nice frontend for iptables.

Something like this:

location ^~ /mysite(?<newaddr>.*) {
  rewrite ^ http://mynewsite.com$newaddr permanent;
}

No need

perusio's picture

to install anything. Is just that a lot of the no-good-for-nothing bots out there don't even bother changing the default UA of the said Perl library.

Just leave as it is. It doesn't block ports. That is done at a lower level of the network stack. You can try a script like arno-iptables-firewall. On Debian you just say which ports you want open and that's it. It already configure things like connection tracking and such.

ufw gives you more control over the rules. A good option.

mm...scheme neutral: location

brianmercer's picture

mm...scheme neutral:

location ^~ /mysite(?<newaddr>.*) {
  rewrite ^ $scheme://mynewsite.com$newaddr permanent;
}

A different twist: no rewrites were harmed ;)

perusio's picture

At the http level define a map:

map $uri $new_uri {
   default '';
    ~^/mysite(?<real_uri>.*)$ $real_uri;
}

Then at the server level define:

if ($new_uri) {
    return 301 $scheme://myserver$new_uri;
}

Yeah. I like the return 301

brianmercer's picture

Yeah. I like the return 301 option as well. The 301 hasn't made the official docs as an option for return yet.

I found a problem in

superfedya's picture

I added this line to my config and able to run .php from anyplace X__X

## Any other attempt to access PHP files returns a 404.
location ~* ^.+\.php$ {
    return 404;
}

My config:

server {
    listen   80;
    server_name  mysite.com;

## Default location
    location / {
        root   /var/www/mysite.com;
        index  index.php;
        try_files $uri @rewrite;

        ## Boost compresses can the pages so we check it. Comment it out
        ## if you don't have it enabled in Boost.
        gzip_static on;

        ## Error page handler for the case where $no_cache is 1. POST
        ## request or authenticated.
        error_page 418 = @no_cache;

        ## If $no_cache is 1 then it means that either we have a session
        ## cookie or that the request method is POST. So serve the dynamic
        ## page.
        if ($no_cache) {
            return 418; # I'm a teapot/I can't get no cachifaction
        }

        ## No caching for POST requests.
        if ($request_method = POST) {
            return 418;
        }

##        try_files /cache/normal/$host/_${args}.html /cache/perm/$host/_.css /cache/perm/$host/_.js /cache/$host/0/.html /cache/$host/0/index.html /index.php;
    }

        location @rewrite {
                # Some modules enforce no slash (/) at the end of the URL
                # Else this rewrite block wouldn't be needed (GlobalRedirect)
                rewrite ^/([^/]*)/(.*)(/?)$ /$1/index.php?q=$2&$args;
        }

## Rewrite forum phpBB3 on ##
    location /forum {
        root   /var/www/mysite.com;
        index  index.php;
rewrite ^/forum/(forum|[a-z0-9_-]*-f)([0-9]+)(-([0-9]+))?\.html$ /forum/viewforum.php?f=$2&start=$4 last;
rewrite ^/forum/(forum|[a-z0-9_-]*-f)([0-9]+)/(topic|[a-z0-9_-]*-t)([0-9]+)(-([0-9]+))?\.html$ /forum/viewtopic.php?f=$2&t=$4&start=$6 last;
rewrite ^/forum/([a-z0-9_-]*)/?(topic|[a-z0-9_-]*-t)([0-9]+)(-([0-9]+))?\.html$ /forum/viewtopic.php?forum_uri=$1&t=$3&start=$5 last;
rewrite ^/forum/resources/[a-z0-9_-]+/(thumb/)?([0-9]+)$ /forum/download/file.php?id=$2&t=$1 last;
rewrite ^/forum/(member|[a-z0-9_-]*-u)([0-9]+)\.html$ /forum/memberlist.php?mode=viewprofile&u=$2 last;
rewrite ^/forum/(member|[a-z0-9_-]*-u)([0-9]+)-(topics|posts)(-([0-9]+))?\.html$ /forum/search.php?author_id=$2&sr=$3&start=$5 last;
rewrite ^/forum/(group|[a-z0-9_-]*-g)([0-9]+)(-([0-9]+))?\.html$ /forum/memberlist.php?mode=group&g=$2&start=$4 last;
rewrite ^/forum/post([0-9]+)\.html$ /forum/viewtopic.php?p=$1 last;
rewrite ^/forum/active-topics(-([0-9]+))?\.html$ /forum/search.php?search_id=active_topics&start=$2&sr=topics last;
rewrite ^/forum/unanswered(-([0-9]+))?\.html$ /forum/search.php?search_id=unanswered&start=$2&sr=topics last;
rewrite ^/forum/newposts(-([0-9]+))?\.html$ /forum/search.php?search_id=newposts&start=$2&sr=topics last;
rewrite ^/forum/([a-z0-9_-]+)(-([0-9]+))\.html$ /forum/viewforum.php?forum_uri=$1&start=$3 last;
rewrite ^/forum/newposts(-([0-9]+))?\.html$ /forum/search.php?search_id=newposts&start=$2&sr=topics last;
rewrite ^/forum/unreadposts(-([0-9]+))?\.html$ /forum/search.php?search_id=unreadposts&start=$2 last;
rewrite ^/forum/the-team\.html$ /forum/memberlist.php?mode=leaders last;

rewrite ^/forum/rss(/(news)+)?(/(digest)+)?(/(short|long)+)?/?$ /forum/gymrss.php?channels&$2&$4&$6 last;
rewrite ^/forum/(news|maps)/?(page([0-9]+)\.html)?$ /forum/map.php?$1&start=$3 last;
rewrite ^/forum/([a-z0-9_-]+)(-([0-9]+))\.html$ /forum/viewforum.php?forum_uri=$1&start=$3 last;
rewrite ^/forum/([a-z0-9_-]+)\.html$ /forum/viewforum.php?forum_uri=$1 last;
rewrite ^/forum/([a-z0-9_-]+)\.html$ /forum/viewforum.php?forum_uri=$1 last;
rewrite ^/forum/.+/(style\.php|ucp\.php|mcp\.php|faq\.php|download/file.php)$ /forum/$1 last;
rewrite ^/forum/.+/(styles/.*|images/.*)/$ /forum/$1 last;
rewrite ^/forum/(news|maps)/([a-z0-9_-]+)(/([a-z0-9_-]+))?/?(page([0-9]+)\.html)?$ /forum/map.php?$2=$4&$1&start=$6 last;
rewrite ^/forum/rss(/(news)+)?(/(digest)+)?(/(short|long)+)?(/([a-z0-9_-]+))?/([a-z0-9_]+)\.xml(\.gz)?$ /forum/gymrss.php?$9=$8&$2&$4&$6&gzip=$10 last;
rewrite ^/forum/([a-z0-9_-]+)(/(news)+)?(/(digest)+)?(/(short|long)+)?/([a-z0-9_]+)\.xml(\.gz)?$ /forum/gymrss.php?nametoid=$1&$3&$5&$7&modulename=$8&gzip=$9 last;
rewrite ^/forum/sitemapindex\.xml(\.gz)?$ /forum/sitemap.php?gzip=$1 last;
rewrite ^/forum/([a-z0-9_]+)-([a-z0-9_-]+)\.xml(\.gz)?$ /forum/sitemap.php?$1=$2&gzip=$3 last;
    }

        # Deny access to internal phpbb files.
        location /forum/(config\.php|common\.php|includes|cache|files|store|images/avatars/upload) {
            deny all;
            # deny was ignored before 0.8.40 for connections over IPv6.
            # Use internal directive to prohibit access on older versions.
            internal;
        }

        # Deny access to version control system directories.
        location ~ /\.svn|/\.git {
            deny all;
            internal;
        }

## Rewrite forum phpBB3 off ##

    location ~ \.php$ {
        try_files $uri =404;
        root           /var/www/mysite.com;
        fastcgi_pass   unix:/tmp/php5-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }


    ## Regular private file serving (i.e. handled by Drupal).
    location ^~ /system/files/ {
        ## Include the specific FastCGI configuration. This is for a
        ## FCGI backend like php-cgi or php-fpm.
        include fastcgi_private_files.conf;
        fastcgi_pass unix:/tmp/php5-fpm.sock;

        ## For not signaling a 404 in the error log whenever the
        ## system/files directory is accessed add the line below.
        ## Note that the 404 is the intended behavior.
        log_not_found off;

    }

    ## Trying to access private files directly returns a 404.
    location ^~ /sites/default/files/private/ {
        internal;
    }

        # Fighting with ImageCache? This little gem is amazing.
        location ~ ^/sites/.*/files/imagecache/ {
                try_files $uri @rewrite;
        }  

    ## Advanced Aggregation module CSS
    ## support. http://drupal.org/project/advagg.
    location ^~ /sites/.*/files/advagg_css/ {
        location ~* /sites/default/files/advagg_css/css_:alnum:+\.css$ {
            access_log off;
            add_header Pragma '';
            add_header Cache-Control 'public, max-age=946080000';
            add_header Accept-Ranges '';
            try_files $uri /index.php?q=$no_slash_uri&$args;
        }
    }     

    ## Advanced Aggregation module JS
    ## support. http://drupal.org/project/advagg.
    location ^~ /sites/.*/files/advagg_js/ {
        location ~* /sites/.*/files/advagg_js/js_[[:alnum:]]+\.js$ {
            access_log off;
            add_header Pragma '';
            add_header Cache-Control 'public, max-age=946080000';
            add_header Accept-Ranges '';
            try_files $uri /index.php?q=$no_slash_uri&$args;
        }
    }

    ## Replicate the Apache <FilesMatch> directive of Drupal standard
    ## .htaccess. Disable access to any code files. Return a 404 to curtail
    ## information disclosure. Hide also the text files.
    location ~* ^(?:.+\.(?:htaccess|make|txt|engine|inc|info|install|module|profile|po|sh|.*sql|test|theme|tpl(?:\.php)?|xtmpl)|code-style\.pl|/Entries.*|/Repository|/Root|/Tag|/Template)$ {
        return 404;
    }

    ## First we try the URI and relay to the @cache if not found.
    try_files $uri @cache;

## We define a named location for the cache.
location @cache {
    ## Boost compresses can the pages so we check it. Comment it out
    ## if you don't have it enabled in Boost.
    gzip_static on;

    ## Error page handler for the case where $no_cache is 1. POST
    ## request or authenticated.
    error_page 418 = @no_cache;

    ## If $no_cache is 1 then it means that either we have a session
    ## cookie or that the request method is POST. So serve the dynamic
    ## page.
    if ($no_cache) {
        return 418; # I'm a teapot/I can't get no cachifaction
    }

    ## No caching for POST requests.
    if ($request_method = POST) {
        return 418;
    }

    # Now for some header tweaking. We use a date that differs
    # from stock Drupal. Everyone seems to be using their
    # birthdate. Why go against the grain?
    add_header Expires "Tue, 13 Jun 1977 03:45:00 GMT";
    # We bypass all delays in the post-check and pre-check
    # parameters of Cache-Control. Both set to 0.
    add_header Cache-Control "must-revalidate, post-check=0, pre-check=0";
    # Funny...perhaps. Egocentric? Damn right!;
    add_header X-Header "Boost Helбs Avril 1.0";
    ## Boost doesn't set a charset.
    charset utf8;

    # We try each boost URI in succession, if every one of them
    # fails then relay to Drupal.
    try_files /cache/normal/$host${uri}_${args}.html /cache/perm/$host${uri}_.css /cache/perm/$host${uri}_.js /cache/$host/0$uri.html /cache/$host/0${uri}/index.html /index.php?q=$no_slash_uri&$args;
}

## We need another named location for the rewrite to work otherwise we
## get the unclean URLs in all their glory.
location @no_cache {
    try_files $uri /index.php?q=$no_slash_uri&$args;
}


##SECURITY##

        ###
        ### deny direct access to backups
        ###
        location ~* ^/sites/.*/files/backup_migrate/ {
                access_log off;
                deny all;
        }

###
### Deny not compatible request methods without 405 response.
###
if ( $request_method !~ ^(?:GET|HEAD|POST|PUT|DELETE|OPTIONS)$ ) {
  return 403;
}

###
### Deny listed requests for security reasons.
###
location ~* (?:delete.+from|insert.+into|select.+from|union.+select|onload|\.php.+src|system\(.+|document\.cookie|\;|\.\.) {
  return 403;
}

###
### Deny some not supported URI like cgi-bin on the Nginx level.
###
location ~* (?:cgi-bin|vti-bin|wp-content) {
  access_log off;
  return 403;
}

        # Very rarely should these ever be accessed outside of your lan
        location ~* \.(txt|log)$ {
                allow 192.168.0.0/16;
                deny all;
        }

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

###
### Send other known php requests/files to php-fpm without any caching.
###
location ~* ^/(?:core/)?(?:boost_stats|update|authorize|rtoc|xmlrpc)\.php$ {
  access_log off;
  try_files $uri =404; ### check for existence of php file first
  fastcgi_pass unix:/tmp/php5-fpm.sock;
}

## Disallow access to .git directory: return 404 as not to disclose
## information.
location ^~ /.git {
    return 404;
}

## Disallow access to patches directory.
location ^~ /patches {
    return 404;
}

## Disallow access to drush backup directory.
location ^~ /backup {
    return 404;
}

## RSS feed support.
location = /rss.xml {
    try_files $uri /index.php?q=$no_slash_uri;
}

## XML Sitemap support.
location = /sitemap.xml {
    try_files $uri /index.php?q=$no_slash_uri;
}

## Support for favicon. Return an 1x1 transparent GIF if it doesn't
## exist.
location = /favicon.ico {
    expires 30d;
    try_files /favicon.ico @empty;
}

## Return an in memory 1x1 transparent GIF.
location @empty {
    expires 30d;
    empty_gif;
}

location = /index.php {
root           /var/www/mysite.com/site;
    ## This is marked internal
    ## http://wiki.nginx.org/HttpCoreModule#internal as pro-active
    ## security practice. No direct access to index.php is allowed all
    ## accesses are made by Nginx from other locations or internal
    ## redirect.
    internal;
    fastcgi_pass unix:/tmp/php5-fpm.sock;

    ## Filefield Upload progress
    ## http://drupal.org/project/filefield_nginx_progress support
    ## through the NgninxUploadProgress modules.
    track_uploads uploads 60s;
}

## Any other attempt to access PHP files returns a 404.
location ~* ^.+\.php$ {
    return 404;
}

}

nginx.conf:

user  www-data;
worker_processes  4;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
    ## epoll is preferred on 2.6 Linux
    ## kernels. Cf. http://www.kegel.com/c10k.html#nb.epoll
    use epoll;
    ## Accept as many connections as possible.
    multi_accept on;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    ## Use sendfile() syscall to speed up I/O operations and speed up
    ## static file serving.
    sendfile        on;

    ## Timeouts.
    client_body_timeout 60;
    client_header_timeout 60;
    keepalive_timeout 10 10;
    send_timeout 60;

    ## Reset lingering timed out connections. Deflect DDoS.
    reset_timedout_connection on;

    ## Handling of IPs in proxied and load balancing situations.
    set_real_ip_from 0.0.0.0/32; # all addresses get a real IP.
    real_ip_header X-Forwarded-For; # the ip is forwarded from the load balancer/proxy

    ## Body size.
    client_max_body_size 10m;

    ## TCP options.
    tcp_nodelay on;
    ## Optimization of socket handling when using sendfile.
    tcp_nopush on;

    ## Compression.
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 1;
    gzip_http_version 1.1;
    gzip_min_length 10;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon application/vnd.ms-fontobject font/opentype application/x-font-ttf;
    gzip_vary on;
    gzip_proxied any; # Compression for all requests.
    ## No need for regexps. See
    ## http://wiki.nginx.org/NginxHttpGzipModule#gzip_disable
    gzip_disable "msie6";

    ## Hide the Nginx version number.
    server_tokens off;

    ## For the filefield_nginx_progress module to work. From the
    ## README. Reserve 1MB under the name 'uploads' to track uploads.
    upload_progress uploads 1m;

    ## Include blacklist for bad bot and referer blocking.
    include blacklist.conf;

    ## Include the map directive that sets the $no_slash_uri variable for drupal 6.
    ## You may comment out the line below if using drupal 7.
    include map_drupal6.conf;

    include map_cache.conf;

    #include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Any help?

That cannot be

perusio's picture

did you add it at the end of the config? Would you be so kind as to show me the debug log where that happens, i.e., after you added that location an execution of a PHP script besides index.php. For example. Adding a foobar.php with:

<?php
phpinfo
();
?>

shouldn't work. Requesting http://mysite.com/foobar.php should return a 404.

Wow

perusio's picture

the hand is faster than the brain. You have a lot of stuff that is completely extraneous to drupal. Namely that blob of rewrites for phpBB. That's where you should look for anything that is not kosher.

You have an error in your config

perusio's picture

named locations (starting with a @) cannot be nested. They must be always at the server level. Move the @rewrite location outside the / location.

That's why there's an error when parsing the config (complain about duplicate try_files).

Done. It works now (about

superfedya's picture

Done. It works now (about duplicate try_files :)
Thank you!

First error is fixed too.
I just put that in this order:

    location ~ .php$ {
        try_files $uri =404;
        root           /var/www/mysite.com;
        fastcgi_pass   unix:/tmp/php5-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_drupal.conf;
    }

## Any other attempt to access PHP files returns a 404.
location ~* ^.+.php$ {
    return 404;
}

Any way to separate phpBB and Drupal config?

Thanks

That's an insecure config

perusio's picture

No need to use it for Drupal at least. Just create another vhost in a subdomain, forum.mysite.com and move all the phpBB stuff there. Reinstate the proper drupal config.

Now if you get hacked it will be through phpBB site, not through drupal :)

Ok, now I understand why it

superfedya's picture

upd. one more problem. With this code index.php in the root doesn't works (error 404):

location = /index.php {
    ## This is marked internal
    ## http://wiki.nginx.org/HttpCoreModule#internal as pro-active
    ## security practice. No direct access to index.php is allowed all
    ## accesses are made by Nginx from other locations or internal
    ## redirect.
    internal;
    fastcgi_pass unix:/tmp/php5-fpm.sock;

    ## FCGI microcache for authenticated users also.
    include sites-available/microcache_fcgi_auth.conf;


    ## Filefield Upload progress
    ## http://drupal.org/project/filefield_nginx_progress support
    ## through the NgninxUploadProgress modules.
    track_uploads uploads 60s;
}

There's no need for it

perusio's picture

i.e., to access index.php. Drupal works perfectly without explicitly addressing index.php.

Example: http://mysite.com/node/123 is what is required. No need for:
http://mysite.com/index.php/node/123.

Anyway if you really need to access index.php just comment out the line with internal.

Thanks I comment out the line

superfedya's picture

Thanks

I comment out the line with internal. But index.php doesn't work.

It works fine when I remove "location = /index.php {".

You have to eliminate the

perusio's picture

~ \.php location to make it work. This is an insecure config. You're not enumerating all the scripts that should be executed. Hence your problem of being able to execute scripts anywhere that you reported above.

Any way to make a redirect

superfedya's picture

Any way to make a redirect from site.com/forum to forum.site.com?

Thanks

Sure

perusio's picture

On the site.com server block (vhost).

location ^~ /forum {
    location ~ ^/forum(?<forum_uri>.*)$
        return 301 http://forum.site.com$forum_uri;
    }
}

With this redirect the urls

superfedya's picture

With this redirect the urls are incorrect.

location ^~ /forum {
    location ~ ^/forum(?<forum_uri>.*)$
        return 301 http://forum.site.com$forum_uri;
    }
}

http://mysite.com/forum/viewtopic.php?f=15&t=23915#p1072963
redirect to:
http://forum.mysite.com/viewtopic.php#p1072963

There is really no any other way to keep the phpBB3 in the /forum directory with separate config?

For rewrites from /site to /
I added:

map $uri $new_uri {
   default '';
    ~^/mysite(?<real_uri>.*)$ $real_uri;
}

and

if ($new_uri) {
    return 301 $scheme://myserver$new_uri;
}

It works great, except for the index (if it's /site/something in works fine, if just from /site to / - page not found.

And many images from imagecache become broken.
This url:
http://mysite.com/sites/default/files/gallerix/albums/1/75/frame/pink-gi...
Become:
http://www.mysite.coms/default/files/gallerix/albums/1/75/frame/pink-gir...

Because /sites it's a standard drupal folder, and this rules remove /site, so mysite.com/sites/file become mysite.coms/file X__X

Any fix for this?

Thanks

Things for the forum are not

superfedya's picture

Things for the forum are not so important. Most important is how to redirect from /site to / without touching /sites directory for the files X_X

Thanks

Try

perusio's picture

map $uri $new_uri {
   default '';
   / $uri;
    ~^/site(?<real_file_uri>/sites.*)$ $real_file_uri;
    ~^/site(?<real_uri>[^s]*)$ $real_uri;
}

When I tried to access my

superfedya's picture

When I tried to access my webpage:
Error: The webpage at http://mysite.com has resulted in too many redirects.

Thanks

Recheck & retry

perusio's picture

I've edited the regex.

Same thing: The webpage at

superfedya's picture

Same thing: The webpage at http://mysite.com has resulted in too many redirects.

Again

perusio's picture

Try it. The edited directive.

Frontpage doesn't work (same

superfedya's picture

Frontpage doesn't work (same error). Other path all working.
Redirect from site/* to /* doesn't works also.

(No subject)

superfedya's picture

My config:

nginx.conf:

user  www-data;
worker_processes  8;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  8192;
    ## epoll is preferred on 2.6 Linux
    ## kernels. Cf. http://www.kegel.com/c10k.html#nb.epoll
    use epoll;
    ## Accept as many connections as possible.
    multi_accept on;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    ## Use sendfile() syscall to speed up I/O operations and speed up
    ## static file serving.
    sendfile        on;

    ## Timeouts.
    client_body_timeout 60;
    client_header_timeout 60;
    keepalive_timeout 10 10;
    send_timeout 60;

    ## Reset lingering timed out connections. Deflect DDoS.
    reset_timedout_connection on;

    ## Handling of IPs in proxied and load balancing situations.
    set_real_ip_from 0.0.0.0/32; # all addresses get a real IP.
    real_ip_header X-Forwarded-For; # the ip is forwarded from the load balancer/proxy

    ## Define a zone for limiting the number of simultaneous
    ## connections nginx accepts. 1m means 32000 simultaneous
    ## sessions. We need to define for each server the limit_conn
    ## value refering to this or other zones.
    ## ** This syntax requires nginx version >=
    ## ** 1.1.8. Cf. http://nginx.org/en/CHANGES. If using an older
    ## ** version then use the limit_zone directive below
    ## ** instead. Comment out this
    ## ** one if not using nginx version >= 1.1.8.
    limit_conn_zone $binary_remote_addr zone=arbeit:10m;

    ## Body size.
    client_max_body_size 10m;

    ## TCP options.
    tcp_nodelay on;
    ## Optimization of socket handling when using sendfile.
    tcp_nopush on;

    ## Compression.
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 1;
    gzip_http_version 1.1;
    gzip_min_length 10;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon application/vnd.ms-fontobject font/opentype application/x-font-ttf;
    gzip_vary on;
    gzip_proxied any; # Compression for all requests.
    ## No need for regexps. See
    ## http://wiki.nginx.org/NginxHttpGzipModule#gzip_disable
    gzip_disable "msie6";

    ## Hide the Nginx version number.
    server_tokens off;

    ## For the filefield_nginx_progress module to work. From the
    ## README. Reserve 1MB under the name 'uploads' to track uploads.
    upload_progress uploads 1m;

map $uri $new_uri {
   default '';
   / $uri;
    ~^/site(?<real_file_uri>/sites.*)$ $real_file_uri;
    ~^/site(?<real_uri>[^s]*)$ $real_uri;
}

    ## Include the map to block HTTP methods.
    include map_block_http_methods.conf;

    ## Include blacklist for bad bot and referer blocking.
    include blacklist.conf;

    ## Include the map directive that sets the $no_slash_uri variable for drupal 6.
    ## You may comment out the line below if using drupal 7.
    include map_drupal6.conf;

    include map_cache.conf;

    ## Microcache zone definition for FastCGI.
    include fastcgi_microcache_zone.conf;

    #include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

mysite.com

# -*- mode: nginx; mode: flyspell-prog;  ispell-current-dictionary: american -*-
### Configuration for example.com.

server {
    listen 80; # IPv4
    server_name www.mysite.com;
    return 301 $scheme://mysite.com$request_uri;
}

## HTTP server.
server {
    listen 80; # IPv4
    server_name mysite.com;
    limit_conn arbeit 32;

    ## Access and error logs.
    access_log /var/log/nginx/example.com_access.log;
    error_log /var/log/nginx/example.com_error.log;

    ## See the blacklist.conf file at the parent dir: /etc/nginx.
    ## Deny access based on the User-Agent header.
    if ($bad_bot) {
        return 444;
    }
    ## Deny access based on the Referer header.
    if ($bad_referer) {
        return 444;
    }

    if ($new_uri) {
       return 301 $scheme://mysite.com$new_uri;
    }

    ## Filesystem root of the site and index.
    root /var/www/sites/mysite.com;
    index index.php;

    ## If you're using a Nginx version greater or equal to 1.1.4 then
    ## you can use keep alive connections to the upstream be it
    ## FastCGI or Apache. If that's not the case comment out the line below.
    fastcgi_keep_conn on; # keep alive to the FCGI upstream



    #################################################################
    ### Configuration for Drupal 6 sites that use boost.
    #################################################################
    include sites-available/drupal_boost6.conf;


    ## For upload progress to work. From the README of the
    ## filefield_nginx_progress module.
    location ~ ^(.*)/x-progress-id:(\w*) {
        return 302 $1?X-Progress-ID=$2;
    }

    location ^~ /progress {
        report_uploads uploads;
    }

} # HTTP server

drupal_boost6.conf;

# -*- mode: nginx; mode: flyspell-prog; ispell-local-dictionary: "american" -*-
### Nginx configuration for using Boost with Drupal. This
### configuration makes use of drush
### (http:///drupal.org/project/drush) for site maintenance and like
### tasks:
###
### 1. Run the cronjobs.
### 2. Run the DB and code updates: drush up or drush upc followed by
###    drush updb to run any DB updates required by the code upgrades
###    that were performed.
### 3. Disabling of xmlrpc.xml, install.php (needed only for
###    installing the site) and update.php: all updates are now
###    handled through drush.


## The 'default' location.
location / {
        root   /var/www/mysite.com;
        index  index.php;

    ## Drupal 404 from can impact performance. If using a module like
    ## search404 then 404's *have *to be handled by Drupal. Uncomment to
    ## relay the handling of 404's to Drupal.
    ## error_page 404 /index.php;

    ## We have to check for boost generated files for the '/' URI.
    ## Also try to use index.html whenever there's no index.php.
    location = / {
        root   /var/www/mysite.com;
        index  index.php;
        ## Boost compresses can the pages so we check it. Comment it out
        ## if you don't have it enabled in Boost.
        gzip_static on;

        ## Error page handler for the case where $no_cache is 1. POST
        ## request or authenticated.
        error_page 418 = @no_cache;

        ## If $no_cache is 1 then it means that either we have a session
        ## cookie or that the request method is POST. So serve the dynamic
        ## page.
        if ($no_cache) {
            return 418; # I'm a teapot/I can't get no cachifaction
        }

        ## No caching for POST requests.
        if ($request_method = POST) {
            return 418;
        }

        try_files /cache/normal/$host/_${args}.html /cache/perm/$host/_.css /cache/perm/$host/_.js /cache/$host/0/.html /cache/$host/0/index.html /index.php;
    }


    ## Using a nested location is the 'correct' way to use regexes.

    ## Regular private file serving (i.e. handled by Drupal).
    location ^~ /system/files/ {
        ## Include the specific FastCGI configuration. This is for a
        ## FCGI backend like php-cgi or php-fpm.
        include fastcgi_private_files.conf;
        fastcgi_pass unix:/tmp/php5-fpm.sock;


        ## For not signaling a 404 in the error log whenever the
        ## system/files directory is accessed add the line below.
        ## Note that the 404 is the intended behavior.
        log_not_found off;
    }

    ## Trying to access private files directly returns a 404.
    location ^~ /sites/default/files/private/ {
        internal;
    }

    ## If accessing an image generated by imagecache, serve it directly if
    ## available, if not relay the request to Drupal to (re)generate the
    ## image.
    location ~* /imagecache/ {
        ## Image hotlinking protection. If you want hotlinking
        ## protection for your images uncomment the following line.
        #include sites-available/hotlinking_protection.conf;

        access_log off;
        expires 30d;
        try_files $uri /index.php?q=$no_slash_uri&$args;
    }


###
### Advagg_css and Advagg_js support.
###
location ~* files/advagg_(?:css|js)/ {
  access_log off;
  expires max;
  rewrite ^/files/advagg_(.*)/(.*)$ /sites/.*/files/advagg_$1/$2 last;
  add_header ETag "";
  add_header Cache-Control "max-age=290304000, no-transform, public";
  add_header Last-Modified "Wed, 20 Jan 1988 04:20:42 GMT";
  add_header X-Header "AdvAgg Generator 1.0";
  set $nocache_details "Skip";
  try_files $uri /index.php?q=$no_slash_uri&$args;

}         

    ## All static files will be served directly.
    location ~* ^.+\.(?:css|js|jpe?g|gif|htc|ico|png|html|xml)$ {
        access_log off;
        expires 30d;
        ## 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;
    }

    ## Replicate the Apache <FilesMatch> directive of Drupal standard
    ## .htaccess. Disable access to any code files. Return a 404 to curtail
    ## information disclosure. Hide also the text files.
    location ~* ^(?:.+\.(?:htaccess|settings\.php$|make|txt|engine|inc|info|install|module|profile|pl|po|sh|.*sql|test|theme|tpl(?:\.php)?|xtmpl)|code-style\.pl|/Entries.*|/Repository|/Root|/Tag|/Template)$ {
        return 404;
    }

    ## First we try the URI and relay to the @cache if not found.
    try_files $uri @cache;
}

## We define a named location for the cache.
location @cache {
    ## Boost compresses can the pages so we check it. Comment it out
    ## if you don't have it enabled in Boost.
    gzip_static on;

    ## Error page handler for the case where $no_cache is 1. POST
    ## request or authenticated.
    error_page 418 = @no_cache;

    ## If $no_cache is 1 then it means that either we have a session
    ## cookie or that the request method is POST. So serve the dynamic
    ## page.
    if ($no_cache) {
        return 418; # I'm a teapot/I can't get no cachifaction
    }

    ## No caching for POST requests.
    if ($request_method = POST) {
        return 418;
    }

    # Now for some header tweaking. We use a date that differs
    # from stock Drupal. Everyone seems to be using their
    # birthdate. Why go against the grain?
    add_header Expires "Tue, 13 Jun 1977 03:45:00 GMT";
    # We bypass all delays in the post-check and pre-check
    # parameters of Cache-Control. Both set to 0.
    add_header Cache-Control "must-revalidate, post-check=0, pre-check=0";
    # Funny...perhaps. Egocentric? Damn right!;
    add_header X-Header "Boost Helбs Avril 1.0";
    ## Boost doesn't set a charset.
    charset utf8;

    # We try each boost URI in succession, if every one of them
    # fails then relay to Drupal.
    try_files /cache/normal/$host${uri}_${args}.html /cache/perm/$host${uri}_.css /cache/perm/$host${uri}_.js /cache/$host/0$uri.html /cache/$host/0${uri}/index.html /index.php?q=$no_slash_uri&$args;
}

## We need another named location for the rewrite to work otherwise we
## get the unclean URLs in all their glory.
location @no_cache {
    try_files $uri /index.php?q=$no_slash_uri&$args;
}

###
### Send other known php requests/files to php-fpm without any caching.
###
location ~* ^/(?:core/)?(?:boost_stats|update|authorize|rtoc|xmlrpc)\.php$ {
  access_log off;
  try_files $uri =404; ### check for existence of php file first
  fastcgi_pass unix:/tmp/php5-fpm.sock;
}

########### Security measures ##########

## Restrict access to the strictly necessary PHP files. Reducing the
## scope for exploits. Handling of PHP code and the Drupal event loop.
## location = /index.php {
    ## This is marked internal
    ## http://wiki.nginx.org/HttpCoreModule#internal as pro-active
    ## security practice. No direct access to index.php is allowed all
    ## accesses are made by Nginx from other locations or internal
    ## redirect.
##  internal;
##    fastcgi_pass unix:/tmp/php5-fpm.sock;

    ## FCGI microcache for authenticated users also.
    include sites-available/microcache_fcgi_auth.conf;

    ## To use Apache for serving PHP uncomment the line bellow and
    ## comment out the above.
    #proxy_pass http://phpapache;
    ## Proxy microcache for autenticated users.
    #include sites-available/microcache_proxy_auth.conf;

    ## Filefield Upload progress
    ## http://drupal.org/project/filefield_nginx_progress support
    ## through the NgninxUploadProgress modules.
##    track_uploads uploads 60s;
##}

        # Deny access to internal phpbb files.
        location ~ /(config\.php|common\.php|includes|cache|files|store|images/avatars/upload) {
        root   /var/www/mysite.com/forum;
            deny all;
            # deny was ignored before 0.8.40 for connections over IPv6.
            # Use internal directive to prohibit access on older versions.
            internal;
        }


###
### Deny not compatible request methods without 405 response.
###
if ( $request_method !~ ^(?:GET|HEAD|POST|PUT|DELETE|OPTIONS)$ ) {
  return 403;
}



###
### Deny listed requests for security reasons.
###
location ~* (?:delete.+from|insert.+into|select.+from|union.+select|onload|\.php.+src|system\(.+|document\.cookie|\;|\.\.) {
  return 403;
}

# Very rarely should these ever be accessed outside of your lan
location ~* \.(txt|log)$ {
    allow 192.168.0.0/16;
    deny all;
}

###
### Deny some not supported URI like cgi-bin on the Nginx level.
###
location ~* (?:cgi-bin|vti-bin|wp-content) {
  access_log off;
  return 403;
}


## Disallow access to .git directory: return 404 as not to disclose
## information.
location ^~ /.git {
    return 404;
}

## Disallow access to patches directory.
location ^~ /patches {
    return 404;
}

## Disallow access to drush backup directory.
location ^~ /backup {
    return 404;
}

###
### deny direct access to backups
###
location ~* ^/sites/.*/files/backup_migrate/ {
    access_log off;
    deny all;
}

## Disable access logs for robots.txt.
location = /robots.txt {
    access_log off;
}

## RSS feed support.
location = /rss.xml {
    try_files $uri /index.php?q=$no_slash_uri;
}

## XML Sitemap support.
location = /sitemap.xml {
    try_files $uri /index.php?q=$no_slash_uri;
}

## Support for favicon. Return an 1x1 transparent GIF if it doesn't
## exist.
location = /favicon.ico {
    expires 30d;
    try_files /favicon.ico @empty;
}

## Return an in memory 1x1 transparent GIF.
location @empty {
    expires 30d;
    empty_gif;
}

    location ~ \.php$ {
        try_files $uri =404;
        root           /var/www/mysite.com;
        fastcgi_pass   unix:/tmp/php5-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_drupal.conf;
    }

## Any other attempt to access PHP files returns a 404.
location ~* ^.+\.php$ {
    return 404;
}

Browser issue?

perusio's picture

Try it with curl.

Where I can find the

superfedya's picture

Where I can find the information about curl? Or some simple?

Thanks

I think there is no way but

superfedya's picture

I think there is no way but move the site to subfolder. But how can I modify the rewrites to use them with subfolder?
/index.php?q=$no_slash_uri&$args;

Thanks

Why don't you explain

perusio's picture

clearly what setup you have? Otherwise is like trying to build Lego robots blindfolded.

  1. How is your site layed out in the filesystem?

  2. Let's stick to Drupal. I have 0 experience with phpBB.

  3. What's the desired result in terms of URI structure?

/var/www/mysite.comI need

superfedya's picture
  1. /var/www/mysite.com (before it was var/www/mysite.com/site).

  2. I need the redirects only for the node's url. All images can have old path (it will work anyway with symlink redirect from /site to /).

I tried to do that with path redirect module, it redirect from /site to / fine, but not for site/* to /*

Maybe it is possible to create a redirect only for /site/nodepath to /nodepath and not for site/image.jpg?

My system: Centos 6, Nginx, Pressflow 6, php-fpm.

Thank you!

First

perusio's picture

the root has very little to do with the URL structure. Did you try:
setting root /var/www/mysite/site and leave the URLs untouched?

The problem it seems to me is that you're trying to solve a root issue with URIs which is
hardly optimal.

If you had

perusio's picture

a site live with that structure and need a redirect now, then it's much simpler.

Just use the map directives + if at server level I wrote. Simple.

So, I just need to change

superfedya's picture

So, I just need to change root in drupal_boost6.conf? There is something to do with nginx.conf?

I think I already tried that and failed :(
Only thing that worked is (http://wiki.nginx.org/Drupal):

        location @rewrite {
                # Drupal in a subdirectory
                rewrite ^/([^/])/(.)(/?)$ /$1/index.php?q=$2&$args;
        }

Path in your config is too different:
try_files $uri /index.php?q=$no_slash_uri&$args;

I think I need to modify:

map $uri $no_slash_uri {
    ~^/(?<no_slash>.*)$ $no_slash;
}

But I don't know how :(

If you

perusio's picture

can wait till next monday, I'll update the config for your use case. It's not very usual, but it happens. I'll have to test it myself.

Thank you! Maybe you can add

superfedya's picture

Thank you! Maybe you can add also this module to your config? http://wiki.nginx.org/HttpImageFilterModule
It can save a low of bandwidth and faster page loading (=better seo).

Thank you Perusio! I finally

superfedya's picture

Thank you Perusio! I finally make a redirect from /site to / based on your config. Before it missed only / after site and myserver.

map $uri $new_uri {
   default '';
    ~^/mysite/(?<real_uri>.*)$ $real_uri;
}

if ($new_uri) {
    return 301 $scheme://myserver/$new_uri;
}

It works for every URL except something like that:
http://mysite.com/node?page=8

And something like that:
http://mysitecom/site/kosplei-2?picture=150
It will redirect to:
http://test.madfanboy.com/kosplei-2

So I think the problem is with ? symbol.

Thank you again. Now the website is very fast!

thats

perusio's picture

because ? is a reserved character for separating the query string from the other URI components.

In order to make abordable you need to guarantee that each way of building query strings don't interfere with one another.

Question: do all URIs that are handled outside drupal are of the form: kosplei-2?picture=<number>?

Question: do all URIs that

superfedya's picture

Question: do all URIs that are handled outside drupal are of the form: kosplei-2?picture=?

No. Outside Drupal nothing. Inside only the node pager (on the frontpage) and Gallerix module.

Thanks you

I think it would be great to

greggles's picture

I think it would be great to get a core issue for this to limit execution of non-essential files in a Drupal installation. There's a similar issue for IIS web.config.

What sort of thing do you have in mind

perusio's picture

there's already a generic reference to this issue on the Wiki.

You mean a more drupal centric thing?

Yeah, exactly. If we shipped

greggles's picture

Yeah, exactly. If we shipped with some Drupal-specific instructions to say "Add index.php and X" to your nginx config. Ideally we'd have something in hook_requirement that sensed an nginx environment and then tested to see how it was configured and threw a warning if they were configured insecurely.

Ok

perusio's picture
  1. Nginx doesn't have a PHP side API that you can use. So the only thing you can use is

    <?php
    $_SERVER
    ['SERVER_SOFTWARE']
    ?>
    to check that we're running Nginx. On my machine:
    at admin/reports/status/php I have: _SERVER["SERVER_SOFTWARE"] nginx/1.3.1
  2. hook_requirements() should try to write a simple PHP file in the files directory
    and try to execute it. If it has success. Generate an error in the status report.

How's that for a start? We should backport to 7 and 6 also I think.

EDIT: the setting of the above global depends on your FastCGI config. The line that generates that is:

fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

Seems like a great plan to

greggles's picture

Seems like a great plan to me.

Nginx

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: