Python is a popular language for web applications, used by Youtube, DropBox, Facebook, Pintrest, Reddit, Instagram, Spotify, Washington Post and many others. Apache is the most popular web server in the world. Mod_wsgi is the recommended way to use Python with Apache.
This article shows you how to write “Hello WSGI”. This is the first step to start creating your own web apps with Python. This is done step by step, testing every single part in the smallest possible step.
Popular Python web frameworks Django and Flask require mod_wsgi for their production installations.
This article show how to install mod_wsgi step by step. We install the smallest testable part at one time.
Prerequisites: to follow this article, you should be familiar with Linux command line basics, installing Linux, sudo and file system hierarchy.
Install Linux
We start with Ubuntu 16.04 LTS amd64. You could perform these steps on a live USB, locally installed system or a remote virtual private server.
This article was prepared with vagrant. You don’t need vagrant to follow this article, but I’ll list here how the test environment setup. With VirtualBox and Vagrant set up: ‘vagrant init bento/ubuntu-16.04; vagrant up; vagrant ssh’
Install Apache
Install Apache
$ sudo apt-get update $ sudo apt-get -y install apache2
Test that it works. You can use Firefox or just command line curl:
$ curl -s localhost|grep title <title>Apache2 Ubuntu Default Page: It works</title>
Test page appears. Let’s remove the test page
$ echo "Hello static" > /var/www/html/index.html
And verify it’s removed
$ curl -s localhost Hello static
Now we have Apache web server installed, and we have replaced the static test page with a page that says “Hello static”.
Install Python 3 mod_wsgi
Install the Python 3 version of the Apache WSGI module. Note the ‘-py3’ ending in the packet name.
$ sudo apt-get install libapache2-mod-wsgi-py3
Restart Apache to use the new module
$ sudo service apache2 restart
Check that your module is enabled
$ apache2ctl -M|grep -i wsgi wsgi_module (shared)
Create Apache VirtualHost for WSGI
With Apache, VirtualHost means a new website in the Apache installation. For example, terokarvinen.com and botbook.com are VirtualHosts in the same Apache installation.
Here, we create and enable a new VirtualHost for use with WSGI Python programming.
With Apache, we first create a VirtualHost in sites-available/, then enable it by linking from sites-enabled and restarting Apache daemon.
Create the new site. The name must end with “.conf”.
$ sudoedit /etc/apache2/sites-available/terowsgi.conf
Write the contents.
<VirtualHost *:80> ServerName terowsgi.example.com WSGIScriptAlias / /home/terowsgi/public_wsgi/tero.wsgi <Directory /home/terowsgi/public_wsgi/> Require all granted </Directory> </VirtualHost>
Only WSGIScriptAlias is specific to WSGI. Otherwise, everything looks just like any VirtualHost setup. In fact, the only change for a static web site would be using DocumentRoot directive in place of WSGIScripAlias.
WSGIScriptAlias tells the ULR path “/” should be handled by a Python programĀ “/home/terowsgi/public_wsgi/tero.wsgi”. So when a users Firefox address bar shows “http://terowsgi.example.com/”, Apache mod_wsgi will call tero.wsgi Python program.
The sites (VirtualHosts) in sites-available/ are not used until they are linked from sites-enabled/. There is a inconvenience command to do this
$ sudo a2ensite terowsgi.conf
Apache has a nice program for checking configuration before trying it for real.
$ apache2ctl configtest Syntax OK
In many cases, “apache2ctl configtest” warns you that your computer is not sure about it’s public name. If you also get “Syntax OK”, that’s nothing to worry about.
We just modified some configuration under /etc/. As always, we must restart the daemon for the changes to take effect.
$ sudo service apache2 restart
Let’s try it out.
$ curl -sH 'Host: terowsgi.example.com' localhost|grep title <title>403 Forbidden</title>
The error messages on the pages, shown to general public, never tell the details. So let’s check the logs!
$ tail -1 /var/log/apache2/error.log [Sun Feb 12 14:48:21.586533 2017] [authz_core:error] [pid 3431:tid 140391693096704] [client ::1:54482] AH01630: client denied by server configuration: /home/terowsgi
Notice that we have not created the user terowsgi yet, so obviously his home directory “/home/terowsgi” or the wsgi script “tero.wsgi” don’t exist yet. But Apache is looking for them.
Create a Project User
Create a new project user. A project user can’t log in, but you other users can be added to his group to be able to edit some of the files in the project user’s home directory. Because project user is used by many humans, it should not be able to log in.
Create the user normally. The easy-to-use user manipulation commands ‘adduser’ and ‘deluser’ commands all start with a verb. Always use a good password, even if we lock it in a moment. Put your own name in the full name field, so that other sudo users know who created this new user.
$ sudo adduser terowsgi Enter new UNIX password: Retype new UNIX password: Full Name []: Tero Karvinen WSGI test User
The new project user terowsgi is not a human, so he is not allowed to log in.
$ sudo usermod --lock terowsgi
To make it possible for other users to be able to edit files in “/home/terowsgi/public_wsgi/”, we create the folder and modify the permissions.
$ sudo chmod u=rwx,g=x,o=x /home/terowsgi $ sudo mkdir /home/terowsgi/public_wsgi $ sudo chown terowsgi.terowsgi /home/terowsgi/public_wsgi
Magic happens here. Obviously, the regular permissions (user, group, others) should make sense. SetGid bit for the group means that newly created files inherit the same group. This way, multiple users can edit the same folder without continuously fiddling with permissions. Don’t give group permissions directly to the home directory “/home/terowsgi/”, only create subdirectories and give permissions there.
$ sudo chmod u=rwx,g=srwx,o=x /home/terowsgi/public_wsgi
Any user we add to the project user’s group will be able to edit the files.
$ sudo adduser $(whoami) terowsgi
New group membership is valid once you have logged out and back. Alternatively, you can use the ‘newgrp’ command.
Let’s test our new powers in terowsgi’s public_wsgi directory.
$ newgrp terowsgi $ groups|grep terowsgi terowsgi adm [...] $ touch /home/terowsgi/public_wsgi/foo $ lsĀ /home/terowsgi/public_wsgi/foo /home/terowsgi/public_wsgi/foo
So we can indeed edit files udner public_wsgi.
$ rm /home/terowsgi/public_wsgi/foo
Write Python WSGI “Hello World”
$ nano /home/terowsgi/tero.wsgi
Write the simplest possible WSGI program.
def application(env, startResponse): startResponse("200 OK", [("Content-type", "text/plain")]) return [b"See you at TeroKarvinen.com\n"]
The name of the function must be “application”. WSGI will automatically call it with two parameters, environment and startResponse function for callback. We can call our parameters whatever we want.
For response, we perform a callback for the given function startResponse. It takes two parameters, a string for http status, and a list of tuples for response header key-value pairs.
Finally, we return a single item list of bytestrings. This bytestring is the body of the reponse.
And try it out. Remember, we have already set up Apache VirtualHost for this site.
$ curl -sH 'Host: terowsgi.example.com' localhost See you at TeroKarvinen.com
Did you see the text “See you at…”? Congratulations, that was your tiny Python program talking. You now have WSGI running.
For actual development with Python WSGI, consider some popular Python web frameworks, such as Django or Flask.
WSGIDaemonProcess baloon user=xubuntu group=xubuntu threads=5 python-path="/home/xubuntu/wsgi/examplecom/"
WSGIScriptAlias / /home/xubuntu/wsgi/examplecom/examplecom/wsgi.py
WSGIProcessGroup baloon
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Require all granted
_VirtualHost *:80_
WSGIDaemonProcess baloon user=xubuntu group=xubuntu threads=5 python-path=”/home/xubuntu/wsgi/examplecom/”
WSGIScriptAlias / /home/xubuntu/wsgi/examplecom/examplecom/wsgi.py
_Directory /home/xubuntu/wsgi/examplecom/_
WSGIProcessGroup baloon
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Require all granted
_/Directory_
_/VirtualHost_
A very good tutorial. No problem following it. Such clarity is rare on the internet.
Would have liked to see one more component at the end, how to address the website externally. Here is what I did:
1. Created an account on DigitalOcean (https://www.digitalocean.com). This is very inexpensive. You get an assigned web address .
2. Followed the tutorial exactly.
3. Copied /etc/apache2/sites-available/000-default.conf to /etc/apache2/sites-available/000-default.conf.hold.
4. Overwrote /etc/apache2/sites-available/000-default.conf with /etc/apache2/sites-available/terowsgi.conf
5. Opened my browser and went to http://.
There may be more sophisticated things one might do but, at a basic level, this approach works fine.
Thanks again for a great tutorial