SaltyCrane: nginxhttps://www.saltycrane.com/blog/2009-11-03T12:04:52-08:00Using Nginx as a caching proxy with Wordpress+Apache
2009-11-03T12:04:52-08:00https://www.saltycrane.com/blog/2009/11/using-nginx-caching-proxy-wordpressapache/<p>We have been evaluating caching reverse proxy servers at work. We looked at
<a href="http://www.igvita.com/2008/02/11/nginx-and-memcached-a-400-boost/">
Nginx+memcached</a>,
<a href="http://www.squid-cache.org/">Squid</a>, and
<a href="http://varnish.projects.linpro.no/">Varnish</a>. Most recently,
we found that <a href="http://wiki.nginx.org/Main">Nginx</a> version 0.7 has
<a href="http://www.ruby-forum.com/topic/183590">support
for caching static files</a> using the <code>proxy_cache</code> directive in
the <a href="http://wiki.nginx.org/NginxHttpProxyModule">NginxHttpProxyModule</a>.
This allows us to use Nginx as a caching proxy without having to handle the
complication (or flexibility depending on how you look at it) of setting and
invalidating the cache as with the Nginx+memcached setup.
Here are my notes for setting it up with an Apache+Wordpress backend.
</p>
<p><em>Update 2010-01-05:</em> Over a couple months, we switched to Nginx 0.8 and we made a few tweaks to our Nginx configuration. Here is our updated conf file: <a href="/site_media/code/nginx_wordpress_100105.conf.txt">nginx_wordpress_100105.conf</a>.
</p>
<h4 id="install">Install Nginx 0.7</h4>
<p>The version of Nginx in Ubuntu is an older version so we used a PPA
created by Jeff Waugh: <a href="https://launchpad.net/~jdub/+archive/ppa">
https://launchpad.net/~jdub/+archive/ppa</a>. (He also has a development
PPA which contains Nginx 0.8.)
</p>
<ul>
<li>Add the following to /etc/apt/sources.list:
<pre>deb http://ppa.launchpad.net/jdub/ppa/ubuntu hardy main
deb-src http://ppa.launchpad.net/jdub/ppa/ubuntu hardy main</pre>
</li>
<li>Tell Ubuntu how to authenticate the PPA
<pre>apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E9EEF4A1</pre>
<p>Alternively, if the keyserver is down, you can follow the instructions
for copying the public key
from <a href="http://forum.nginx.org/read.php?2,5177,11272">
http://forum.nginx.org/read.php?2,5177,11272</a>.
</p>
</li>
<li>Install Nginx from new PPA
<pre>apt-get update
apt-get install nginx</pre>
</li>
<li>Check the version of Nginx
<pre>nginx -V</pre>
<pre>nginx version: nginx/0.7.62
configure arguments: --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-debug --with-http_stub_status_module --with-http_flv_module --with-http_ssl_module --with-http_dav_module --with-http_gzip_static_module --with-ipv6 --with-http_realip_module --with-http_xslt_module --with-http_image_filter_module --with-sha1=/usr/include/openssl</pre>
</li>
</ul>
<h4 id="configure-logging">Configure Nginx cache logging</h4>
<p>Within the <code>http {}</code> block, add:</p>
<pre>
log_format cache '***$time_local '
'$upstream_cache_status '
'Cache-Control: $upstream_http_cache_control '
'Expires: $upstream_http_expires '
'"$request" ($status) '
'"$http_user_agent" ';
access_log /var/log/nginx/cache.log cache;
</pre>
<h4 id="configure-backends">Nginx configuration for backend servers</h4>
<p>Within the <code>http {}</code> block, add:</p>
<pre>
include /etc/nginx/app-servers.include;
</pre>
<p>And <code>/etc/nginx/app-servers.include</code> looks like:</p>
<pre>
upstream backend {
ip_hash;
server 10.245.275.88:80;
server 10.292.150.34:80;
}
</pre>
<h4 id="configure-paths">Configure cache path/parameters</h4>
<p>Within the <code>http {}</code> block, add:</p>
<pre>
proxy_cache_path /var/www/nginx_cache levels=1:2
keys_zone=one:10m;
inactive=7d max_size=200m;
proxy_temp_path /var/www/nginx_temp;
</pre>
<h4 id="more-config">More proxy cache configuration</h4>
<p>We added
the username from the <em>wordpress_logged_in_*</em> cookie as part of the cache key
so that different logged in users will get the appropriate page from
the cache. However, our Wordpress configuration sends HTTP headers disabling
the cache when a user is logged in so this is actually not used. But it does
not hurt to include this, in case we change our Wordpress configuration
in the future.
</p>
<p>Within the <code>server {}</code> block, add:</p>
<pre>
location / {
# capture cookie for use in cache key
if ($http_cookie ~* "wordpress_logged_in_[^=]*=([^%]+)%7C") {
set $my_cookie $1;
}
proxy_pass http://backend;
proxy_cache one;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$my_cookie;
proxy_cache_valid 200 302 304 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid any 1m;
}
</pre>
<h4 id="non-cached-locations">Configure locations that shouldn't be cached</h4>
<p>If WordPress sends the appropriate HTTP Cache-Control headers, this step
is not necessary. But we have added it to be on the safe side.
Within the <code>server {}</code> block, add:</p>
<pre>
location /wp-admin { proxy_pass http://backend; }
location /wp-login.php { proxy_pass http://backend; }
</pre>
<h4 id="restart-nginx">Restart Nginx</h4>
<p>The Nginx reverse proxy cache should work without modification to the
Apache configuration. In our case, we had to disable WP Super Cache
because we had been using that previously.</p>
<pre>/etc/init.d/nginx restart</pre>
<h4 id="view-log">View the log</h4>
<p>Check the <code>/var/log/nginx/cache.log</code> to see if everything is
working correctly. The log should diplay <em>HIT</em>, <em>MISS</em>,
and <em>EXPIRED</em> appropriately. If the log shows only misses, check
the Cache-Control and Expires HTTP headers that are sent from Apache+Wordpress.
</p>
<h4 id="cache-headers">Example Apache/Wordpress configuration that disabled the Nginx cache</h4>
<p>Part of the WP Super Cache configuration included the following in the
<code>.htaccess</code> file. It had to be removed for Nginx cache the pages.
(In particular, the <em>must-revalidate</em> part had to be removed.)
</p>
<pre> Header set Cache-Control 'max-age=300, must-revalidate'</pre>
<h4 id="links">Links</h4>
<ul>
<li><a href="http://wiki.nginx.org/NginxHttpProxyModule">
NginxHttpProxyModule Documentation</a></li>
<li><a href="http://forum.nginx.org/search.php?0,search=proxy_cache,author=,page=1,match_type=ALL,match_dates=0,match_forum=2,match_threads=0">
Nginx forum search of the English Mailing list for "proxy_cache"</a></li>
</ul>
Notes on using nginx with mod_python and Django
2009-04-28T00:26:11-07:00https://www.saltycrane.com/blog/2009/04/notes-using-nginx-mod_python-and-django/<p>Here are my notes on setting up
<a href="http://nginx.net/">nginx</a> as a reverse proxy with
Apache, mod_python, and Django on Ubuntu Intrepid. Nginx is used
to serve my static media files while all other requests are passed
on to the Apache/mod_python/Django web server.
</p>
<p>I realize
<a href="http://code.google.com/p/modwsgi/">mod_wsgi</a> has become the
<a href="http://simonwillison.net/2009/Apr/1/modwsgi/">preferred</a>
<a href="http://collingrady.wordpress.com/2009/01/06/mod_python-versus-mod_wsgi/">
way</a> to deploy Django, but I'm a little behind the times and am still
using mod_python. I hope to switch to mod_wsgi soon.
</p>
<p>I <a href="http://www.saltycrane.com/blog/2008/12/card-store-project-4-notes-using-amazons-cloudfront/">
have been using</a> Amazon's
<a href="http://aws.amazon.com/cloudfront/">CloudFront</a> service for
delivering my static media files. As far as I can tell, it has worked well.
My main reason for switching to nginx is so I can skip the extra step of
uploading to Amazon. Regarding my concern about the memory footprint of nginx,
it looks like it is using around 5mb with my two process configuration.
</p>
<p>My configuration parameters are shown below. I'm running two sites, SaltyCrane and
HandsOnCards on a <a href="http://www.slicehost.com/">Slicehost</a> 256mb plan.
</p>
<table width=550>
<tr>
<td><b>Description</b></td>
<td><b>HandsOnCards</b></td>
<td><b>SaltyCrane</b></td>
</tr>
<tr>
<td>Redirection</td>
<td><code class="code2">http://www.handsoncards.com</code> is redirected to <code class="code2">http://handsoncards.com</code></td>
<td><code class="code2">http://saltycrane.com</code> is redirected to <code class="code2">http://www.saltycrane.com</code></td>
</tr>
<tr>
<td>Static media filesystem path</td>
<td><code class="code2">/srv/HandsOnCards/handsoncards/static/</code></td>
<td><code class="code2">/srv/SaltyCrane/iwiwdsmi/media/</code></td>
</tr>
<tr>
<td>Static media web path</td>
<td><code class="code2">/site_media/</code></td>
<td><code class="code2">/site_media/</code></td>
</tr>
<tr>
<td>Django settings.py file location</td>
<td><code class="code2">/srv/HandsOnCards/handsoncards/settings.py</code></td>
<td><code class="code2">/srv/SaltyCrane/iwiwdsmi/settings.py</code></td>
</tr>
<tr>
<td>Additional Python packages path</td>
<td><code class="code2">/srv/python-packages</code></td>
<td><code class="code2">/srv/python-packages</code></td>
</tr>
</table>
<h4>Install nginx</h4>
<p>Some recommend installing nginx from source, but I took the easier route
and used Ubuntu's package manager.</p>
<pre>sudo apt-get install nginx</pre>
<h4>Nginx configuration</h4>
<p>Edit <code>/etc/nginx/nginx.conf</code>:</p>
<pre class="nginx">user www-data www-data;
worker_processes 2;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
keepalive_timeout 3;
tcp_nodelay off;
gzip on;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript text/xml
application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name www.handsoncards.com;
rewrite ^/(.*) http://handsoncards.com/$1 permanent;
}
server {
listen 80;
server_name handsoncards.com;
access_log /var/log/nginx/handsoncards.com.access.log;
error_log /var/log/nginx/handsoncards.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy.conf;
}
location /site_media/ {
alias /srv/HandsOnCards/handsoncards/static/;
expires 24h;
}
}
server {
listen 80;
server_name saltycrane.com;
rewrite ^/(.*) http://www.saltycrane.com/$1 permanent;
}
server {
listen 80;
server_name www.saltycrane.com;
access_log /var/log/nginx/saltycrane.com.access.log;
error_log /var/log/nginx/saltycrane.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy.conf;
}
location /site_media/ {
alias /srv/SaltyCrane/iwidsmi/media/;
expires 24h;
}
}
}
</pre>
<p>Edit <code>/etc/nginx/proxy.conf</code>:</p>
<pre class="nginx">proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;</pre>
<p>Restart Nginx</p>
<pre>sudo /etc/init.d/nginx restart</pre>
<h4>Install Apache</h4>
<p>I already had Apache and mod_python installed, but in case you don't:</p>
<pre>apt-get install apache2 apache2-mpm-prefork
apt-get install libapache2-mod-python</pre>
<h4>Apache Configuration</h4>
<p>Edit <code>/etc/apache2/httpd.conf</code>:</p>
<pre>MaxClients 2
MaxRequestsPerChild 350
KeepAlive Off
NameVirtualHost 127.0.0.1:8080
Listen 8080
<VirtualHost 127.0.0.1:8080>
ServerName www.saltycrane.com
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE iwiwdsmi.settings
PythonPath "['/srv/SaltyCrane', '/srv/python-packages'] + sys.path"
PythonDebug Off
</Location>
</VirtualHost>
<VirtualHost 127.0.0.1:8080>
ServerName handsoncards.com
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE handsoncards.settings
PythonPath "['/srv/HandsOnCards', '/srv/python-packages'] + sys.path"
PythonDebug Off
</Location>
</VirtualHost></pre>
<p>Edit <code>/etc/apache2/ports.conf</code> and comment out the following two lines:</p>
<pre>#NameVirtualHost *:80
#Listen 80</pre>
<p>Restart Apache</p>
<pre>sudo /etc/init.d/apache2 restart</pre>
<h4>Add Django's reverse proxy middleware</h4>
<p>Edit your <code>settings.py</code> file to include
<code class="code2">django.middleware.http.SetRemoteAddrFromForwardedFor</code> in
<code>MIDDLEWARE_CLASSES</code>. This allows your Django application to use the real
IP address of the client instead of <code>127.0.0.1</code> from your nginx proxy. It
sets Django's <code>request.META['REMOTE_ADDR']</code>
to be <code>request.META['HTTP_X_FORWARDED_FOR']</code> which we set above in nginx's
<code>proxy.conf</code>. For more information, see the
<a href="http://docs.djangoproject.com/en/dev/ref/middleware/#reverse-proxy-middleware">
Middleware reference</a> in the Django documentation.
</p>
<pre>MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.middleware.http.SetRemoteAddrFromForwardedFor',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)</pre>
<h4>References</h4>
<ul>
<li><a href="http://bart.whahay.net/blog/2009/04/06/setting-up-nginx-django.html">
http://bart.whahay.net/blog/2009/04/06/setting-up-nginx-django.html</a> (my main reference)</li>
<li><a href="http://articles.slicehost.com/2009/3/5/ubuntu-intrepid-nginx-configuration">
http://articles.slicehost.com/2009/3/5/ubuntu-intrepid-nginx-configuration</a> (general nginx configuration)</li>
<li><a href="http://articles.slicehost.com/2009/3/6/ubuntu-intrepid-nginx-virtual-hosts-1">
http://articles.slicehost.com/2009/3/6/ubuntu-intrepid-nginx-virtual-hosts-1</a> (nginx virtual hosts configuration)</li>
<li><a href="http://forum.slicehost.com/comments.php?DiscussionID=2964">
http://forum.slicehost.com/comments.php?DiscussionID=2964</a> (explains the
difference between <code>root</code> and <code>alias</code> in nginx.conf.)</li>
</ul>
<h4>Errors</h4>
<ul>
<li>A 502 Bad Gateway from Nginx probably means there is something wrong with your Apache setup</li>
<li>An Internal Server Error from Apache means something is probably wrong with your Django
setup.</li>
</ul>