Nginx Rewrite Rules for WP Admin over SSL

This is a continuation of administering your WordPress blog over SSL to increase your blog security.

Nginx rewrite rules are tricky, but are easier to learn once you understand them. I’m sure they are lot easier to understand, learn and write than .htaccess rules for Apache HTTP server. Here I solve an important issue when you use WordPress over HTTPS, otherwise called the secure protocol.

The Problem

Once you modified the wp-config.php file as per the instructions on the other post, all the administration would happen via HTTPS. But, it doesn’t stop the visitors to browse the site through secure protocol. You wouldn’t want this happen, especially for search engines as they see duplicate content now.

The Solution – Nginx Rewrite Rule

To overcome this, we can write a simple rewrite rule in Nginx to redirect the regular visitors and search engines to browse regular posts via port 80. Here is how to do this…

On your site’s configuration file…

server {
  listen 443;
  server yourdomain.com;

  # Regular rules to manage WordPress over SSL such as
  location /wp-admin {
     # proxy_pass or fastcgi_pass rule/s
  }

  # Put this as the last line
  # To redirect regular pages to HTTP
  location / { rewrite ^ http://$host$request_uri permanent; }
}

Do you have any questions or need clarification regarding the above Nginx rewrite rule to manage WordPress over secure protocol? Please do write them as a comment. I’m glad to assist you.

Updated (on August 16, 2012), after the request from Don:

Here is the working example code. To see this for yourself, please visit http://ssl.pothi.info. You may try log into the backend. You will get a SSL warning, because the SSL certificate is valid only for my primary domain (pothi.info). There are multiple ways to achieve the same configuration. This is just my way. :)

server {
  listen 80;
  server_name ssl.pothi.info;
  root /path/to/wordpress;
  index index.php;

  location ~ \.php$ {
    # Request to wp-login and wp-admin to go via HTTPS protocol
    location ~ /wp-(admin|login) {
      return 301 https://$host$request_uri;
    }

    # Process non-admin requests
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass  unix:/var/lock/php-fpm;
    fastcgi_intercept_errors on;
  }

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

}

server {
  listen 443 ssl;
  server_name ssl.pothi.info;

  ssl_certificate xyz.crt;
  ssl_certificate_key xyz.key;

  root /path/to/wordpress;
  index index.php;

  # Process only the requests to wp-login and wp-admin
  location ~ /wp-(admin|login) {
    location ~ \.php$ {
      try_files $uri =404;
      include fastcgi_params;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_pass  unix:/var/lock/php-fpm;
      fastcgi_intercept_errors on;
    }
  }

  # Redirect everything else to port 80
  location / {
    return 301 http://$host$request_uri;
  }
}

25 Replies to “Nginx Rewrite Rules for WP Admin over SSL”

  1. Can you please post a working example of a config you have with this? I am having trouble integrating this into my config.

    Thanks

  2. Hey Pothi, had one more question for you if you could help. I am trying to do the following with my nginx config:

    server {
    
            listen       80;
    
            server_name_in_redirect off;
            server_name  .example.com;
    
            root /var/www/wp;
            index index.php;
            autoindex off;
    
            access_log /var/log/nginx/example_access.log;
            error_log /var/log/nginx/example_error.log;
    
            location / {
                    rewrite ^.*/files/(.*) /wp-includes/ms-files.php?file=$1;
                    if (!-e $request_filename)
                    {
                            rewrite ^.+?/?(/wp-.*) $1 last;
                            rewrite ^(.+)$ /index.php;
                    }
            }
    
            location ~\.php {
                    try_files $uri = 404;
                    include        /etc/nginx/fastcgi_params;
                    fastcgi_pass   unix:/var/run/php5-fpm.sock;
                    fastcgi_param  SCRIPT_FILENAME  /var/www/wp$fastcgi_script_name;
            }
    
            # rewrite all 403 to 404
            error_page 403 = 404;
    
            # deny all access to .dot files
            location ~ /\. { access_log off; log_not_found off; deny all; }
    
            # deny access to files starting with a $, these are usually temp files
            location ~ ~$ { access_log off; log_not_found off; deny all; }
    
            # keep logs clean by not logging access to favicon.
            location = /favicon.ico { access_log off; log_not_found off; }
    
            # keep logs clean by not logging access to robots.txt
            location = /robots.txt { access_log off; log_not_found off; }
    
            # deny access to wp-config.php
            location ~* wp-config.php {
                    allow 192.168.1.20;
                    deny all;
            }
    
            # protect wp-admin/wp-login
            set $noadmin 1;
    
            if ($remote_addr = "192.168.1.20")   { set $noadmin 0; }
    
            if ($noadmin = 1) {
                  rewrite ^/wp-admin/((?!admin-ajax\.php).*)$ /index.php permanent;
                  rewrite ^/wp-([^/]*?).php(.*)$ /index.php permanent;
            }
    
            # rewrite /wp-admin with a trailing slash.
            rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    
    }
    
    
    server {
    
            listen 443 ssl;
    
            server_name_in_redirect off;
            server_name .example.com;
    
            root /var/www/wp;
            index index.php;
            autoindex off;
    
            access_log /var/log/nginx/example_ssl_access.log;
            error_log /var/log/nginx/example_ssl_error.log;
    
            ssl_certificate      example.crt;
            ssl_certificate_key  example.key;
            ssl_protocols        SSLv3 TLSv1 TLSv1.1 TLSv1.2;
            ssl_ciphers          HIGH:!aNULL:!MD5;
    
            location ~ /wp-(admin|login|includes|content) {
                    location ~ \.php$ {
                            try_files $uri = 404;
                            include fastcgi_params;
                            fastcgi_index index.php;
                            fastcgi_pass   unix:/var/run/php5-fpm.sock;
                            fastcgi_param  SCRIPT_FILENAME  /var/www/wp$fastcgi_script_name;
                    }
            }
    
            # redirect everyone back to the non-ssl page
            location / { return 301 http://$host$request_uri; }
    
            # rewrite all 403 to 404
            error_page 403 = 404;
    
            # deny all access to .dot files
            location ~ /\. { access_log off; log_not_found off; deny all; }
    
            # deny access to files starting with a $, these are usually temp files
            location ~ ~$ { access_log off; log_not_found off; deny all; }
    
            # keep logs clean by not logging access to favicon.
            location = /favicon.ico { access_log off; log_not_found off; }
    
            # keep logs clean by not logging access to robots.txt
            location = /robots.txt { access_log off; log_not_found off; }
    
            # deny access to wp-config.php
            location ~* wp-config.php {
                    allow 192.168.1.20;
                    deny all;
            }
    
            # protect wp-admin/wp-login
            set $noadmin 1;
    
            if ($remote_addr = "192.168.1.20")   { set $noadmin 0; }
    
            if ($noadmin = 1) {
                  rewrite ^/wp-admin/((?!admin-ajax\.php).*)$ /index.php permanent;
                  rewrite ^/wp-([^/]*?).php(.*)$ /index.php permanent;
            }
    
            # rewrite /wp-admin with a trailing slash.
            rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    
    }
    

    I can’t seem to get my sub domain sites to work under SSL. I can get the main site to work but every time I try to hit the subdomain site I get redirected to the non-ssl page. When I force the subdomain page to SSL using https://subdomain.com I get a broken admin page with broken CSS. Any ideas what I could be doing wrong?

    Thanks

  3. Hi Pothi :)

    Since switching to CloudFlare, I’ve been forced to disable SSL administration since CF won’t proxy SSL. I’ve been searching for some way to have wp-admin and wp-login redirect to a different subdomain (like ssl.domain.com) for administration functions. Modifying your rewrite rule (return 301 https://ssl.$host$request_uri;) I was able to successfully reach the wp-login page under the ssl subdomain, but right after login in, I’m being redirected to https://domain.com/wp-admin again, which CloudFlare won’t proxy. Do you think we can find a way to make this work?

    Cheers,
    Guigo

      1. Hey Pothi, thanks for your reply :)

        Well, I managed to get this working with the help of WordPress HTTPS plugin. All you have to do is set up a Nginx server block for your subdomain (ex. ssl.domain.com) and set this as your SSL Host in the plugin settings. No rewrite rules needed. Anyway, thanks a lot and keep your nice work!

        Namaskar,
        Guigo

        1. That was amazing to know, Guigo. I use that plugin too, but didn’t know there are multiple ways to use it.

          Thanks for sharing the solution.

          Pothi

        2. Hi Guigo,

          I was trying to do the same thing as you, but I can’t seem to get it to work. I set up WordPress HTTPS plugin as you’ve suggested (do I have to check Force SSL Administration?) and a Nginx server block for the subdomain. I removed the rewrite rules for wp-admin and wp-login from the non-HTTPS server block of Nginx. But when I tried to login to WordPress, it just keeps loading the login page. Would appreciate if you can give some advice or share your Nginx config for the subdomain.

          Thanks!

  4. I noticed the ssl configuration you suggest does not include wp-includes – which was noticeable mainly because CSS was broken and chrome’s inspector showed some 403s for missing content. Simply update your ssl location line with:

    location ~ /wp-(admin|login|includes) {

    I think that’s all that’s necessary. Maybe those are new assets? I’m on WordPress 3.8.3.

  5. I also face this issue. not work above code for me . I am using latest Webuzo CP with NGINX.
    I want only /wp-admin/ and wp-login.php will redirect to 443 port
    I want /wp-admin/ and wp-login.php will not redirect to 80 port from 443 port

        1. If the file gets downloaded means… there is no `location` block to process PHP. That in turn means something is missing to send the request to PHP. Please make sure you have `fastcgi_pass` line in both server blocks.

          Btw, I’ve migrated the Github repo to https://github.com/pothi/wordpress-nginx/ . You’re probably using the older configurations. Kindly check the relevant config in the new repo at https://github.com/pothi/wordpress-nginx/blob/master/sites-available/login-over-ssl.conf . Thanks!

  6. Hope somebody still gets notifications from comments made here…

    I’ve followed the instructions and get a ‘clean’ secure login, but when I’m logged in I get mixed content errors.

    Any clues?

    1. Thanks for your comment. Mixed content errors aren’t related to Nginx. They are based on how the application (WordPress) is designed or developed. Solution to fix those errors depend on a lot of factors. If you search “how to fix mixed content error in WordPress” using your favorite search engine, you may get some ideas.

Leave a Reply

Your email address will not be published. We use cookies to prevent spam comments. Required fields are marked *

css.php