What good a website with a “Welcome to nginx” note? That’s where we left last time.
My primary reference for this Apache to Nginx migration was this article — in fact, my configs are more or less a copy-paste from this guide.
For your convenience I’ll just repeat the steps here…
Configuring the Nginx vhost
Since it’s always nice to save a backup of the original default config files before we make any changes — because it’s easy to roll back to the reference point and troubleshoot when something goes wrong — we move the original nginx.conf
file as follows:
# mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf-org
Then create a new /etc/nginx/nginx.conf
file and insert the following text in it:
user www-data; worker_processes 1; pid /var/run/nginx.pid; events { worker_connections 1024; # multi_accept on; } http { sendfile on; tcp_nopush on; tcp_nodelay on; server_tokens off; include mime.types; default_type application/octet-stream; index index.php index.htm index.html redirect.php; #Gzip gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_disable "MSIE [1-6].(?!.*SV1)"; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; #FastCGI fastcgi_intercept_errors on; fastcgi_ignore_client_abort on; fastcgi_buffers 8 16k; fastcgi_buffer_size 32k; fastcgi_read_timeout 120; fastcgi_index index.php; limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; #Our individual site vhost server files will live here }
The worker_processes 1
directive above is of special importance here. I have the worker_processes
set to 1 simply because the VPS I’m using currently offers me one CPU. It’s usually safe to set the worker_processes
number to the number of processor cores you have. Like, for example, under a Rackspace Cloud Server install, I’ll have it set to 4, simply because I’m offered that many virtual cores.
Second, if you notice, the nginx.conf
above doesn’t have any WordPress specific configs yet. However, if you look at the last statement, it basically tells Nginx to refer to the /etc/nginx/sites-enabled/
directory for these instead.
If you do an ls
inside of /etc/nginx/
the default DotDeb install gives your two directories that we all must take note of /etc/nginx/sites-enabled/
as mentioned in our /etc/nginx.conf
file, and more importantly the /etc/nginx/sites-available/
directory.
This second directory is where we’ll have our vhost configs, while in the former we’ll simply have individual vhost configs files’ symlinks. This gives us the option to disable a site while keeping the original config files intact by simply deleting the symlinks — and Nginx thinks the site is gone.
Do an ls
inside /etc/nginx/sites-available/
, and you’ll notice a default
file already present. This same file is also simlinked inside /etc/nginx/sites-enabled/default
. Open this file and you notice a location of a file: /usr/share/nginx/www/index.html
. The content of this file follows:
<html> <head> <title>Welcome to nginx!</title> </head> <body bgcolor="white" text="black"> <center><h1>Welcome to nginx!</h1></center> </body> </html>
Remember the “Welcome to nginx!” message from the concluding screenshot of part 1? This /etc/nginx/sites-enabled/default
file is a useless location, yet an excellent starting point to understand Nginx vhost configurations.
Let us now create our WordPress vhost config — in my case it’s /etc/nginx/sites-available/opensourceforu.com
— and insert the following text:
server { listen 80; server_name opensourceforu.com www.opensourceforu.com; root /srv/www/opensourceforu.com/public; access_log /srv/www/opensourceforu.com/logs/access.log; error_log /srv/www/opensourceforu.com/logs/error.log; client_max_body_size 8M; client_body_buffer_size 128k; #The section below contains your WordPress rewrite rules location / { try_files $uri $uri/ /index.php?q=$uri&$args; } location /search { limit_req zone=one burst=3 nodelay; rewrite ^ /index.php; } fastcgi_intercept_errors off; location ~* \.(ico|css|js|gif|jpe?g|png)$ { expires max; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } #sample 301 redirect # location /2011/06/26/permalink/ { # rewrite //2011/06/26/permalink/ http://example.com/2011/06/27/permalink_redirecting_to/ permanent; # } #Send the php files to upstream to PHP-FPM #This can also be added to separate file and added with an include location ~ \.php$ { try_files $uri =404; #This line closes a big security hole #see: http://forum.nginx.org/read.php?2,88845,page=3 fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_pass 127.0.0.1:9000; } location /wp-admin { auth_basic "Administrator Login"; auth_basic_user_file /srv/www/opensourceforu.com/.htpasswd; } #!!! IMPORTANT !!! We need to hide the password file from prying eyes # This will deny access to any hidden file (beginning with a .period) location ~ /\. { deny all; } #Once you have your w3-total.conf file ready uncomment out the line below include w3-total.conf; }
—————–
Now, let’s quickly go through the essential portions of this file.
- In line #4, we give Nginx the domain name of our website.
- In line #5, we tell Nginx which directory to load our website from — the webroot.
- In line #6 and 7, we tell it where to save the access and error logs.
- Line #12 to 21 hold the rewrite rules. Remember, we use
mod_rewrite
in Apache to take care of URL rewriting for WordPress permalinks? In Apache we do it using.htaccess
files. Nginx doesn’t support.htaccess
, so we write the rewrites in the vhost config itself. This way we don’t lose our permalink structure once we’ve migrated the WordPress directory over from our old Apache host. - Line #31 holds a very important statement, to protect our website from arbitrary PHP code execution. (A lot of WordPress-based Nginx configs available on the Internet miss out on this.)
- Line #57 to 60 hold the directive for password protecting the
/wp-admin/
folder with a Web server-level password. This is just an additional layer of security before the WP authentication. - Line #64 hides any
.
(dot) files from prying eyes. Although our.htpasswd
file is not inside the webroot, but since we’ll migrate the files from an Apache server we’d obviously like to hide all the.htaccess
files that comes along with it that are hidden inside many subdirectories. - Finally in line #67, we define where W3 Total Cache should save its configurations. It usually does it in the
.htaccess
file of webroot automatically under Apache. But again since Nginx doesn’t support.htaccess
as we’ve discussed, we define it specifically in in this vhost config file.
With that done. Let us create our webroot folder structure. Note that we’re not configuring it inside /usr/share/nginx/www/
, but under /srv/
. Under a fresh Debian install, this directory is empty.
# mkdir -p /srv/www/opensourceforu.com/{public,logs}
So, now we have /srv/www/opensourceforu.com/public/
, which will serve as our webroot for opensourceforu.com, and /srv/www/opensourceforu.com/logs/
, where Nginx saves the access and error logs.
Finally let’s create the /etc/nginx/w3-total.conf
file that we’ll need later (as we defined in the vhost config above).
# touch /etc/nginx/w3-total.conf
Migration time
Time to migrate the WordPress files from the Apache server…
Note that if you’re configuring the Nginx server on the same host, you simply need to point to the correct WordPress install directory in the vhost config file above in line #5 and 6.
Since, for me, it was from MediaTemple to this VPS, and also since MediaTemple gives me shell access with rsync
facility, it was a simple recursive rysnc
for me.
In case you don’t have shell access to your old server, or your host doesn’t provide you with rsync
facility, the the job becomes a bit tiresome — that is, having to download all the files over FTP. I somehow don’t like FTP, so I don’t touch shared hosting facilities that don’t provide me with rsync
.
rysnc --progress -ruvpa <apache-server-username>@opensourceforu.com:path/to/webroot/ /srv/www/opensourceforu.com/public/
Maybe you can take a latest mysqldump of database before running the rsync
command above — a plugin like WP-DBManager is a handy resource for the same. So, what happens in this case is, you rsync
over the latest DB dump along with the other files.
Refer back to the /etc/nginx/nginx.conf
file — note that the nginx
daemon runs as www-data:www-data
. However, our webroot — /srv/www/opensourceforu.com/public
— is root:root
. Let’s correct that so that Nginx/WordPress has no issues writing to the webroot.
chown -R www-data:www-data /srv/www/opensourceforu.com/public
Remeber, W3 Total Cache also needs to write its configurations, so we change the ownership of this file too:
chown www-data:www-data /etc/nginx/w3-total.conf
All good.
Now that the WordPress files are in place, type to generate the password to secure the /wp-admin/
directory. The easier way is to simply install the htpasswd
utility, part of the apache2-utils
package.
# apt-get install apache2-utils
No need to panic. This package doesn’t pull the main Apache server as a dependency.
Generate the htpasswd
:
htpasswd -c /srv/www/opensourceforu.com/.htpasswd <htpassds-username>
The location of the .htpasswd
file could be anywhere as long as it corresponds with the location mentioned in the vhost file.
Database import and MySQL tuning
Although you can drive the website without altering any settings in MySQL, the following tunings are handy if you have a 2GB RAM VPS.
Let’s first save a backup of the default /etc/mysql/my.cnf
file:
mv /etc/mysql/my.cnf /etc/mysql/my.cnf-bak
Now create an empty /etc/mysql/my.cnf
file and append the following directives:
[client] port = 3306 socket = /var/run/mysqld/mysqld.sock [mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = 0 [mysqld] user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp language = /usr/share/mysql/english skip-external-locking key_buffer = 16M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 16 myisam-recover = BACKUP max_connections = 100 table_cache = 256 thread_concurrency = 2 ## Try number of CPU's*2 query_cache_limit = 4M query_cache_size = 128M general_log_file = /var/log/mysql/mysql.log general_log = 1 log_slow_queries = /var/log/mysql/mysql-slow.log long_query_time = 2 log-queries-not-using-indexes expire_logs_days = 10 max_binlog_size = 100M [mysqldump] quick quote-names max_allowed_packet = 16M [isamchk] key_buffer = 16M # * IMPORTANT: Additional settings that can override those from this file! # The files must end with '.cnf', otherwise they'll be ignored. # !includedir /etc/mysql/conf.d/
We’ve just ended up supplying some good default memory power to our MySQL server.
Time to import the database — but first, we need to create the database to import the data into:
# mysql -u root -p Enter password:
Enter the root password that you’ve set when you installed the mysql-server-5.5
packages in the earlier article. Once you get the mysql>
prompt after authentication, create a new MySQL user and a database on which this user has rights:
mysql> CREATE DATABASE lfydb; Query OK, 1 row affected (0.00 sec) mysql> GRANT ALL PRIVILEGES ON lfydb.* TO "lfy_user_name"@"localhost" IDENTIFIED BY "password"; Query OK, 0 rows affected (0.00 sec) mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.01 sec) mysql> EXIT
Now import the current database — if you used a plugin like WP-DBManager with it’s default settings, the location should be something like this: /srv/www/opensourceforu.com/wp-content/backup-db/1328724761_-_databasename.sql
Import this dump into your freshly created database as follows:
mysql -u lfy_user_name -p -h localhost lfydb < /srv/www/opensourceforu.com/public/wp-content/backup-db/1328724761_-_databasename.sql
It will ask for lfy_user_name’s password. It will take some time before returning you the shell prompt — depends on the size of the database.
Finally, open your /srv/www/opensourceforu.com/public/wp-config.php
file, and make sure the database name, user, password and hostname corresponding to the new DB we created and imported the dump onto just now:
// ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define('DB_NAME', 'lfydb'); /** MySQL database username */ define('DB_USER', 'lfy_user_name'); /** MySQL database password */ define('DB_PASSWORD', 'password'); /** MySQL hostname */ define('DB_HOST', 'localhost');
All good — reload MySQL service:
# /etc/init.d/mysql reload
Back on WordPress to make final changes
Let us enable our vhost first — remember we need to create the correct symlink:
ln -s /etc/nginx/sites-available/opensourceforu.com /etc/nginx/sites-enabled/opensourceforu.com
Reload Nginx service with the new config:
/etc/init.d/nginx reload
Time to now check the website! However, before making changes permanent to your domain’s DNS service, we will edit our local machine’s (not the VPS — but the desktop or laptop we’re using) /etc/hosts
file to cheat the browser into bypassing a DNS check to open opensourceforu.com
:
<Nginx-VPS-IP-ADDRESS> opensourceforu.com www.opensourceforu.com
Save and close… run a ping
test on your local machine to confirm that opensourceforu.com
resolves to the IP address of the Nginx VPS.
Time to finally launch your browser, and login to the WordPress backend — you should be greeted by the htpasswd
authentication first :-)
Validate, and you get your WordPress login page. Once you authenticate, first thing to do is add the nginx Compatibility plugin. Quoting the plugin’s page will make it clear why this is necessary:
The plugin solves two problems:
- When WordPress detects that FastCGI PHP SAPI is in use, it disregards the redirect status code passed to wp_redirect. Thus, all 301 redirects become 302 redirects which may not be good for SEO. The plugin overrides wp_redirect when it detects that nginx is used.
- When WordPress detects that mod_rewrite is not loaded (which is the case for nginx as it does not load any Apache modules) it falls back to PATHINFO permalinks in Permalink Settings page. nginx itself has built-in support for URL rewriting and does not need PATHINFO permalinks. Thus, when the plugin detects that nginx is used, it makes WordPress think that mod_rewrite is loaded and it is OK to use pretty permalinks.
Good things is, this is a zero-setup plugin — and takes care of the above two points after a simple activation. However, it drops in two plugins, and by default activates “nginx Compatibility (PHP4)”. Since, we’re using PHP5, deactivate that and active “nginx Compatibility (PHP5)” as you can see in the following screenshot.
Finally, access your W3 Total Cache settings… and you should be greeted by a lot of red warnings. Ignore them and scroll down to the bottom of the page.
W3 Total Cache automatically detects that the Web server as Nginx, and since it can’t have the easy way around of writing configs to .htaccess
anymore, it will present you with a box to fill in the location of “Nginx server configuration file path”. Since we defined the location as /etc/nginx/w3-total.conf
in the vhost file, we enter the full path for the same here — as you can see in the screenshot below.
Save settings.
Scroll to the top and click all the “auto-install” buttons on all those red warning boxes. Unfortunately, I don’t have a screenshot handy for that — but it basically implies that the specified settings are not available in the text config file for WordPress to refer to. (You don’t lose any of this plugin-specific custom settings because it also has the settings saved in the DB, and that’s why the prompt — this plugin is smart!) Clicking auto-install simply dumps the details, which it otherwise saves on a .htaccess
in Apache, to our /etc/nginx/w3-total.conf
location.
The best part: if you’re coming from a server environment where APC was not available, make W3 Total Cache’s opcode and db cache dropdowns to APC :-)
Go back to VPS SSH terminal, open /etc/php5/conf.d/apc.ini
, and append the following settings (taken as it is from the tutorial I referred at the beginning of the article):
; configuration for php apc module extension = apc.so apc.enabled = 1 apc.shm_segments = 1 apc.shm_size = 512M apc.optimization = 0 apc.num_files_hint = 2700 apc.user_entries_hint = 2700 apc.ttl = 7200 apc.user_ttl = 3600 apc.gc_ttl = 600 apc.cache_by_default = 1 apc.slam_defense = 1 apc.use_request_time = 1 apc.mmap_file_mask = /dev/zero apc.file_update_protection = 2 apc.enable_cli = 0 apc.max_file_size = 2M apc.stat = 1 apc.write_lock = 1 apc.report_autofilter = 0 apc.include_once_override = 0 apc.rfc1867 = 0 apc.rfc1867_prefix = "upload_" apc.rfc1867_name = "APC_UPLOAD_PROGRESS" apc.rfc1867_freq = 0 apc.localcache = 1 apc.localcache.size = 1350 apc.coredump_unmap = 0 apc.stat_ctime = 0
Reload the php5-fpm
service
# /etc/init.d/php5-fpm reload
Hopefully, you shouldn’t encounter any errors. Go back to your browser — to the settings page of W3 Total Cache and clear all cache.
View your site! We’re done here!
Finally, open your CDN administrator settings and enter the IP address of your VPS — I had to since we use MaxCDN as an Origin Pull CDN. And then go to your domain’s DNS server admin area and enter the new IP — since opensourceforu.com is propagated by CloudFlare it took less than 15 minutes before I could remove the /etc/hosts
file hack from my local system and was accessing the website from the new VPS — pages served by Nginx :-)
BTW, here’s some benchmark test (using the ab
utility that also comes as part of the apache2-utils
package that we’d installed for the htpassed
utility earlier) — for 500 simultaneous connections 10,000 times:
$ ab -n 10000 -c 500 https://www.opensourceforu.com/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking www.opensourceforu.com (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: nginx Server Hostname: www.opensourceforu.com Server Port: 80 Document Path: / Document Length: 75493 bytes Concurrency Level: 500 Time taken for tests: 3.086 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 758990000 bytes HTML transferred: 754930000 bytes Requests per second: 3240.81 [#/sec] (mean) Time per request: 154.282 [ms] (mean) Time per request: 0.309 [ms] (mean, across all concurrent requests) Transfer rate: 240209.24 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 5.6 0 30 Processing: 28 43 15.3 39 268 Waiting: 20 42 14.9 39 268 Total: 35 44 18.3 40 268 Percentage of the requests served within a certain time (ms) 50% 40 66% 40 75% 40 80% 41 90% 41 95% 69 98% 122 99% 131 100% 268 (longest request)
That’s quite impressive on a standard 2GB VPS with one CPU core :-) Try to replicate the same results on Apache.
Dealing with logs
One final step is to set the correct webroot path so that logroate
can also rotate and compress the logs for our Nginx’s “access” and “error” logs located at the non-standard location /srv/www/opensourceforu.com/logs/
. Open /etc/logrotate.d/nginx
, and make sure it reads like this:
/srv/www/opensourceforu.com/logs/*.log { daily missingok rotate 52 compress delaycompress notifempty create 0640 www-data adm sharedscripts prerotate if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ run-parts /etc/logrotate.d/httpd-prerotate; \ fi; \ endscript postrotate [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid` endscript }
That’s all folks!
on linux
. I don’t believe wordpress could use nginx. Wooow. It’s cool.
Recently, I have been working on nginx configuration for python based website, am using nginx with fastcgi and web.py as a beginning. Your article helped me understand some of the points.
[…] https://www.opensourceforu.com/2012/02/wordpress-nginx-part-2-domain-vhost-config-migrating-files-fine-tun… Leave a Comment TrackBack URI […]
[…] easiest thing to do is lock down the wp-admin folder with an htpasswd (as we showed you in an earlier article on Nginx). On Apache you follow a similar directive.It’s even easier on a shared hosting environment. […]
Super helpful…everything was working and I was trying to set up W3TC. I saw the APC instructions, and there was no apc.ini. So I did an “apt-get install php-apc” and now all I can see are 502 Bad Gateway pages on my sites. Help!
And….nevermind. Apache started back up, Varnish was in the mix too. Sheesh. Up and running again, but haven’t quite gotten w3 fully operational yet.
It appears Page Cache URL rewriting is not working. If using apache, verify that the server configuration allows .htaccess or if using nginx verify all configuration files are included in the configuration.It appears Minify URL rewriting is not working. If using apache, verify that the server configuration allows .htaccess or if using nginx verify all configuration files are included in the configuration.
W3TC minify with APC usually creates url rewrite issues. Change it to disk and see if that fixes it. I typically use APC with object and db cache. (you can right click and check the source of this webpage).
The authentication placed on /wp-admin work fine if all you request is /wp-admin/ but if you ask for a specific file such as /wp-admin/install.php then the authentication never gets triggered.
Thoughts?
I googled a bit more and found the solution as shown below:
location ~ ^/wp-admin { auth_basic “Administrator Login”; auth_basic_user_file /opt/mysite/www/.wp-passwd; }
Real good catch. Never tried to access files names directly inside /wp-admin — like /wp-admin/install.php. Never worried about it because I block any access to wp-admin and wp-login.php from varnish itself.
However, location ~ ^/wp-admin doesn’t work — at least not on my setup where I tested.
A more fool-proof implementation would be adding another block under it as follows:
location /wp-admin { auth_basic “Administrator Login”; auth_basic_user_file /srv/www/opensourceforu.com/.htpasswd; }
location ~ ^/wp-admin/.*.php$ { auth_basic “Administrator Login”; auth_basic_user_file /srv/www/opensourceforu.com/.htpasswd; try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; include fastcgi_params; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; #fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_pass 127.0.0.1:9010; }
See the section: “When the Web server is Nginx” in https://www.opensourceforu.com/2012/04/how-to-lock-down-wordpress-admin-access-using-socks5-proxy/
Edit: Just realised, the code gets scrambled… do take a look at the above link.
@Atanu Datta
First good article but its a borring process to run same commands all the time when we prepare a new server. We need a some automation on this which can do this repeated task for us for example imagine a shell script (EasyEngine) which setup entine nginx php mysql postfix and other in one go, automatically set worker_processes as per no of cpu available and other stuff.
Also provide wordpress caching option like w3-total-cache wp-super-cache and nginx-fastcgi cache.
So while the new server setup we can enjoy coffee :)
EasyEngine Homepage: http://rtcamp.com/easyengine/
Github: https://github.com/rtCamp/easyengine
thanks a lot man.. specially about the … u know what ALL were great
Wow, this article is good, my sister is analyzing
such things, so I am going to convey her.