Dreamier Dream Server with Nginx
2009/2/13 An updated version of this article using Ubuntu Intrepid can be viewed here.
In my previous setup there were some concerns with the way I was using mod_proxy. The issue is that a request comes in and is received by Apache, if it was a media file Apache would then pass the request to Lighttpd, which would pass the file to Apache, which would then pass the file to the requester. Obviously, this is batshit crazy.
This is why I have reworked my instructions with fewer catastrophic mental lapses. The article has also been reworked for conciseness, and all sections on security have been removed, you can refer back to the original article, but should ideally look for a more specialized resource on securing your server.
The bulk of the process is unchanged, but we will now be using Nginx as a frontend proxy and static media server, and Apache2 with mod_python as the backend server for handling Django. Lighttpd has been dropped in favor of Nginx mostly because Nginx is from Russia, and is thus exotic.
Also relevant is the fact that Lighttpd has an annoying memory leak.
End Product
The end product is still an Ubuntu Feisty server. It will use Nginx as its frontend server (handling media, and proxying other requests to Apache2), and Apache2 as its backend server. It will use memcached for caching, and Postgres8.2 for its database. Django will be our framework of choice.
On to the fun stuff.
Credits and resources can be found in the original article.
Upgrading Ubuntu Dapper to Feisty
Upgrading from Ubuntu Dapper (SliceHost's current Ubuntu OS) to Feisty is quick and painless. You do need to do the upgrades in order: skipping straight to Feisty may make your installation unstable. The final 'lsb_release -a' is simply to confirm that the upgrade has occured. It should confirm that Feisty is installed.
### My article
- My list entry one
- My list entry two
<code class="python">def x (a, b):
return a * b</code>
Adding Apache & Other Libraries
Note: psyco_pg2 in the Ubuntu repository appears to be broken, which is why we are installing it with easy_install instead of apt-get.
### My article
- My list entry one
- My list entry two
@@ python
def x (a, b):
return a * b
Setting up nginx
First we need to setup nginx. We will be using the configuration file provided in this article at blog.kovyrin.net. The file is available for download right underneath the sytax highlighted code snippet.
Open up your nginx.conf file:
import myproject.myapp.markdownpp as markdownpp
Then completely delete the contents of the file, and replace them with the contents of the configuration file that we are borrowing from kovyrin.net.
You will have to make several minor changes to the config file:
- Replace "some-server.com www.some-server.com" with your actual domain.
- For the static file location change the long regular expression to /media/
- Change the root directory in the static media location to /var/www/yourdomain.com/
So the first altered part will look like this
class Entry(models.Model):
title = models.CharField(maxlength=200)
body = models.TextField()
body_html = models.TextField(blank=True, null=True)
use_markdown = models.BooleanField(default=True)
(Yes, I acknowledge how totally unhelpful it is to change the example from some-server.com to yourdomain.com.)
And the second portion will look like
def save(self):
if self.use_markdown:
self.body_html = markdownpp.markdown(self.html)
else:
self.body_html = self.body
super(Entry,self).save()
We are changing the regular expression to /media/ because we no longer have to perform a number of regular expression matches. Admittedly, the performance increase is probably minimal.
Now lets create a few folders:
class Resource(models.Model):
title = models.CharField(maxlength=50)
markdown_id = models.CharField(maxlength=50)
content = models.FileField(upload_to="myapp/resource")
Okay, now we deal with Apache.
Setting up Apache
The first step is to change the port apache is running on:
class Entry(models.Model):
title = models.CharField(maxlength=200)
body = models.TextField()
body_html = models.TextField(blank=True, null=True)
use_markdown = models.BooleanField(default=True)
Change it to 8080 instead of 80. Save the file.
Now we need to make our log folder:
def save(self):
if self.use_markdown:
pieces = [self.html,]
for res in Resource.objects.all():
ref = u'\n\n[%s]: %s "%s"\n\n' % (
res.markdown_id,
res.get_content_url(),
res.title,
)
pieces.append(res)
content = u"\n".join(pieces)
self.body_html = markdownpp.markdown(content)
else:
self.body_html = self.body
super(Entry,self).save()
Then we'll need to edit our apache config file:
refs = entry.resources.all()
That will initially be an empty file, and you'll be adding this to it:
def save:
super(Entry,self).save()
res = self.resources.all()
# etc etc
super(Entry,self).save()
Finally we need to enable the virtual host:
import time, thread
from django.db import models
from django.dispatch import dispatcher
from django.db.models import signals
#######################
### your models go here ###
#######################
Starting nginx and Apache
Now that we have our two servers running, we need to turn them on.
def resave_object(sender, instance, signal, *args, **kwargs):
def do_save():
time.sleep(3)
try:
instance.save()
except:
pass
id = unicode(instance) + unicode(instance.id)
try:
should_resave = resave_hist[id]
except KeyError:
resave_hist[id] = True
should_resave = True
if should_resave is True:
resave_hist[id] = False
thread.start_new_thread(do_save, ())
else:
resave_hist[id] = True
Go visit www.yourserver.com:8080 and you should get a default Apache page.
Go visit www.yourserver.com and... you should still get a default Apache page. Only this time if you look at the headers you'll be getting it served via nginx.
Now lets double check that nginx is serving static media like we want it to:
resave_hist = {}
dispatcher.connect(resave_object, signal=signals.post_save, sender=Entry)
Write a couple words into the file, save it, and then browse to www.yourserver.com/media/test.txt . It should show your file. Browse to www.yourserver.com, it shouldn't show the apache page.
Now turn Apache back on:
and delete that file
And our servers are setup.
PostgreSQL
Make sure to use your own password instead of just 'password' in the code below.
qaodmasdkwaspemas15ajkqlsmdqpakldnzsdfls
And then we edit the pg_hba.conf file.
qaodmasdkwaspemas16ajkqlsmdqpakldnzsdfls
Go to the end of the file and comment out all lines that start with host (unless you will be accessing your database remotely). Finally the local line should look like
qaodmasdkwaspemas17ajkqlsmdqpakldnzsdfls
Finally we'll want to restart Postgres:
qaodmasdkwaspemas18ajkqlsmdqpakldnzsdfls
Configuring memcached
Running memcached is easy:
qaodmasdkwaspemas19ajkqlsmdqpakldnzsdfls
On Ubuntu the user www-data is the user that runs the webserver, making it very similar to the apache user on some other distributions. Running memcached with the -u www-data option means that we'll be running memcached with the same user as the webserver. Port 11211 is the default for memcached, and probably should not be changed unless you are running multiple memcached instances. I chose to start my memcached instance with 32 megs of memory because my slice only has 256 meg total, and my Django app simply doesn't have very much information to cache.
Next we need to get python-memcached, which is a python memcached client. There is an alternate cmemcached library for Python that is twice as fast as python-memcached, but I had trouble getting it to compile (I believe because I installed memcached from a repository instead of from source). Python-memcached is easy to get:
qaodmasdkwaspemas20ajkqlsmdqpakldnzsdfls
And that should be that.
At this exact moment (July 12, 2007) the current svn version of Django is broken with python-memcached. A patch has been submitted and is working its way through the submission system, so hopefully this issue will resolve itself soon. If anyone is affected by this situation, send me an email or leave a comment and I'll update this guide to include the required modifications.
Setting up Django, etc
First lets create a non-root user, it'll hold all our django files. For these examples it will be user django.
qaodmasdkwaspemas21ajkqlsmdqpakldnzsdfls
Now we are going to setup Django. We will first create a postgres user for our Django app:
qaodmasdkwaspemas22ajkqlsmdqpakldnzsdfls
Hint: Write down the database table, user and password we'll be using them again in a couple of minutes, just far enough in the future to completely forget them all.
Now we need to give the www-data user access to our files (www-data is the user that runs the web server).
qaodmasdkwaspemas23ajkqlsmdqpakldnzsdfls
Then we'll want to create some folders for Django. You can feel free to play with the folder layout, its mostly a personal thing, but you'll have to keep any changes you make in mind when you follow the remaining instructions.
qaodmasdkwaspemas24ajkqlsmdqpakldnzsdfls
Now we check out the Django source and link the checked out source into the site-packages so that the python interpreter can find it.
qaodmasdkwaspemas25ajkqlsmdqpakldnzsdfls
Now we get to create our first Django project. Notice how we symlink the project into the site-packages folder so that it available in the python path. This is a matter of preference, you can alteratively add the path to the project to the Apache virtual host file we edited earlier.
qaodmasdkwaspemas26ajkqlsmdqpakldnzsdfls
Now edit your newly minted settings.py file.
qaodmasdkwaspemas27ajkqlsmdqpakldnzsdfls
You'll need to make a number of changes, these are the lines you will need to add (not alter) to your settings.py:
qaodmasdkwaspemas28ajkqlsmdqpakldnzsdfls
I personally use a very long middleware cache because my pages don't change much (I am serving a blog, and thus caching entire pages is not too problematic), the standard value is much lower, around 300. The key prefix is used to distinguish caches between multiple Django projects using the same caching backend. If you are only planning to have one project using your caching backend then it is fine to leave the key as an empty string. If you plan on having multiple projects, each should have a unique key prefix. Finally, the anonymous only option means that logged in users will not recieve cached pages. For my application, where only the admin will ever be logged in, this is an appropriate setting, your milleage may vary.
And here are the already existing lines you will need to modify in settings.py:
qaodmasdkwaspemas29ajkqlsmdqpakldnzsdfls
Extending this Django setup later: you will eventually want to add your own templates and your own media along with your own appl ication. To provide access to your media files you will want to create a symlink from wherever they are into your /var/www/yourdomain.com/media folder like this:
qaodmasdkwaspemas30ajkqlsmdqpakldnzsdfls
For templates you'll just need to append a path to your template directory to the TEMPLATE_DIRS
variable in the setting s.py file.
Configuring Django applications and projects is sometimes more of an art than a science. Go try painting for a while, but feel free to ask for help if you need it.
Quick Fix To For Apache & Python Egg Compatibility
Because we installed psycopg2 via a python egg we will need to add a couple lines to our Apache config file. This process is explained on the Django page for modython, but is fairly simple.
First make a file to hold a few lines of Python:
qaodmasdkwaspemas31ajkqlsmdqpakldnzsdfls
Now eggs.py should contain these lines:
qaodmasdkwaspemas32ajkqlsmdqpakldnzsdfls
Then we need to open our virtual host file
qaodmasdkwaspemas33ajkqlsmdqpakldnzsdfls
And modifying the file to look like this:
qaodmasdkwaspemas34ajkqlsmdqpakldnzsdfls
Save the file and now Apache and eggs should play nicely together.
Weeping with Joy
We're done. Of course your server isn't secure at all, you should go do something about that. While working through these instructions you probably ran into points where you thought "What the hell is wrong with this guy?" and also "This doesn't work... at all." Well I hope you remember those moments, because I'd appreciate knowing which spots caused emotional distress (so I can idly dream about fixing them).