Running a Flask application under the Apache WSGI module

Python is a fantastic programming language and Flask is an equally fantastic micro framework for Python… at least as far as I can tell from my experiences so far. However, as I’m sure you are already aware, Python just like Ruby, is not a native web language, like PHP is. So to get your Python code running on a production web server can be tricky.

The Flask website does detail ways of getting your code deployed on Apache, CGI, Fast CGI and multiple standalone WSGI containers. However I found the Flask documentation to be incomplete and not very helpful. Clearly from the amount of complaints on stack overflow, I’m not the only one. So today I’m focusing on deploying to an Apache server, for all you lovely people.

Development Environment

First I’m going to start by creating a development environment, the Flask docs cover this fine, however I’m going to reiterate here as well for completeness.

First we need to install Python, which I hope you have already done. If you haven’t it’s dead simple, here is a quick and simple guide.

We also need to make sure the Apache WSGI module and it’s prerequisites are installed and then restart Apache.

$ sudo aptitude install apache2 apache2.2-common apache2-mpm-prefork apache2-utils libexpat1 ssl-cert

$ sudo aptitude install libapache2-mod-wsgi

$ /etc/init.d/apache2 restart

Next you need to install Pip, this is a dependency manager tool and this is what we will use to install Flask. It’s so simple, here is another quick and simple guide.

Next we want to install Flask

$ pip install Flask

For the purpose of keeping this tutorial focused, I’m only showing how to install the Flask package globally on your system. If you want to create separate environments with different dependencies I recommend using the virtualenvwrapper tool

If the Pip install command fails, it means you have permission issues, you can either fix these, or if you’re really lazy, you can run as sudo.

So now Pip is installed globally on you system lets create a simple application in the form of a single Python script. You can put this script anywhere you like. However for this example I have put mine here: /var/www/flask-dev/__init__.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello world!"

if __name__ == "__main__":
    app.run(debug=True)

This is all pretty simple stuff, the import at the top is importing the Flask package that we just installed with Pip. Flask is then creating starting a web server, by default this will be on port 5000. It’s also binding the hello function to the base directory.

If you now execute this Python script by running

$ python /var/www/flask-dev/__init__.py

Your console will tell you the web server has been started and if you point your browser now to localhost:5000 you will see the hello function is executed and the browser will display the text “Hello World”…… Simples.

Production Environment

Now lets assume you have finished building your new, awesome, world changing application. You don’t want to have to tell everyone, “Go check out my awesome new tool @ www.someawesometool.com:5000” that would be pretty lame, right. Not to mention to other drawbacks of using a development environment in production.

Here I am going to show how we can use an Apache module called WSGI (Web Server Gateway Interface), to point a host name to your Flask application.

First things first, lets create the exact same application file but this time here: /var/www/flask-prod/webtool.py, I’m also turning debug mode off.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello world!"

if __name__ == "__main__":
    app.run()

Next you want to create a new host record in your hosts file (/etc/hosts)

127.0.0.1 my.webtool

What this will do, is tell any request to my.webtool (e.g. from your browser) to redirect to your localhost, which Apache will pick up and handle accordingly.

Next we are going to create a WSGI file, this is a file which Apache will use to access your Flask application. Like I was saying earlier, Python isn’t natively a web language and so this WSGI file allows Apache to interact with Python as if it’s native. It’s a simple script. You can put it anywhere, but for the sake of simplicity I am going to put it in the web root. Lets call it /var/www/flask-prod/webtool.wsgi

import sys

sys.path.append('/var/www/flask-prod')

from webtool import app as application

Yes that’s it, the first line is simply importing the system module. Line 2 we are adding the web root path (where the Flask application is located) into the system path. Line 3 is essentially importing the app variable we created in webtool.py and aliasing it to the application namespace. Apache executes this file after getting a request to our new virtualhost and then does it’s magic from there. It is very important that whatever you name the webtool.py file, you must use the same name in the from area on line 5 in you WSGI file. “from webtool” is really saying “from webtool.py”

Lastly let’s create your virtual host, this should go here: /etc/apache2/sites-available/my.webtool

<virtualhost *:80>
    ServerName my.webtool

    WSGIDaemonProcess webtool user=www-data group=www-data threads=5 home=/var/www/flask-prod/
    WSGIScriptAlias / /var/www/flask-prod/webtool.wsgi

    <directory /var/www/flask-prod>
        WSGIProcessGroup webtool
        WSGIApplicationGroup %{GLOBAL}
        WSGIScriptReloading On
        Order deny,allow
        Allow from all
    </directory>
</virtualhost>

This will capture all “my.webtool” requests on your server and redirect them to the WSGI file that we just created. The WSGI file is then sending the request onto the Flask application which produces a HTTP response.

Some important points to consider in this virtual host file are:

  1. The first argument of the WSGIDaemonProcess command (webtool) should have the same value as WSGIProcessGroup in the Directory block. These are the name of the process and process group and should be unique on your system.
  2. You should set the home attribute of the WSGIDeamonProcess to be the path of you WSGI file, the same path should also be provided in the Directory block.
  3. The user and group which are specified in WSGIDaemonProcess are a Unix user and group, the user and group you specify should have execute privileges on the WSGI script.

Now just enable the new virtual host and restart apache

$ sudo a2ensite my.webtool

$ /etc/init.d/apache2 restart

Now you can simply point your browser to http://my.webtool/ and you should get the response “Hello World!”. If you get an internal server error then you unfortunately have done something wrong. Your best bet is to check the Apache error logs which can usually be located at /var/log/apache2/error.log. Alternatively you can start over and take a things a little slower. Don’t rush it, this does work.

Conclusion

That just about sums up everything you need to know about getting Flask to run in a production environment. If you have any questions I’ll be happy to try and help. I’ve not tried these steps on OS X yet, however I’m pretty sure the process is going to be almost identical. Python is installed by default, however you may need to use a packaging tool like Homebrew of Macports to install the WSGI Apache module.

Thanks for reading

47 Love This

49 Comments Running a Flask application under the Apache WSGI module

  1. Pingback: Running a Flask application under the Apache WS...

  2. Simon

    Hi, my.webtool is the hostname/virtualhost for your Flask application. You can think of it like a domain, in a sense. The only difference is that instead of having a FQDN, you have a host which is internal to your machine. Without creating separate virtualhosts like my.webtool, you wouldn’t be able to run multiple websites from a single Apache instance, because Apache wouldn’t know where to forward the traffic it is receiving. Hope that helps.

    Reply
    1. Nguyen

      Dear Sir,

      I cannot use any words to describe my feeling rightnow. It really hard to choose words to express my feeling.
      Only one simple phrase I can write down here is “thanks for your tutorial.”

      Wish you best!

      Reply
  3. Simon

    Hi Evan, sorry for my incredibly slow reply. I’ve had a lot of personal issues the last few months.

    If you haven’t solved this already, which I’m sure you have. Double check you have a file called my.webtool inside the /etc/apache2/sites-available directory. You may have accidentally put the file inside /etc/apache2/sites-enabled.

    That would have the same effect as running the a2ensite command.

    I hope that helps.

    Reply
  4. psudo

    Hey so is this process only for a production server then? Im starting developing locally on mac osx and using pyvenv (python3.5). In the venv I have pip(by default), flask, and mod_wsgi installed. I have macs apache2 set up to serve from the ~/user/Sites directory, and mysql installed. I guess right now Im a little confused on figuring out how to tie all this together. I can get all 3 servers to work by themselves, but if Im not mistaken I need to get modwsgi to point the flask server to the apache one via .conf files? Also, from the docs:

    https://pypi.python.org/pypi/mod_wsgi

    says that with modwsgi-express it auto sets up configuration for you, which with that and pyvenv it really makes things convenient. I feel Im close to figuring this out. I guess my question would be how do I know if Im routing the flask app through apache with wsgi properly? Thanks

    Reply
    1. Simon

      Hi, firstly I’v really sorry about replying to your question so late. I’m sure you’ve solved this problem now. I actually wrote an article on setting flask up on OSX – http://www.jakowicz.com/setting-up-apache-wsgi-module-on-osx-10-9/

      Both articles are for setting up production systems.However there is nothing wrong with using this approach in development, so it mirrors production more accurately.

      Ae you still having a problem here? Again, sorry for the incredibly slow reply.

      Reply
  5. Paul Huygen

    Thanks for this tutorial!

    One remark:

    The browser on my (Ubuntu 16.04) computer can only find the site my.webtool if it is listed in /etc/hosts as:

    127.0.0.1 my.webtool

    (i.e. IP-addres first).

    Cheers,

    Paul

    Reply
    1. Simon

      Hi Paul,

      You’re welcome and thank you for pointing out that error. My example hosts file entry was the wrong way round. I’ve fixed this now. Cheers.

      Reply
  6. anon

    2 years after your blog post: your procedure is still extremely helpful. documentation of flask+apache2 connection is still sketchy.

    Reply
    1. Simon

      I’ll be completely honest, I never would have thought this article would be so popular. It was a painful process 2 years ago and it’s a shame the official documentation is still below par, but I’m glad you and everyone else is still finding the content useful :)

      Reply
  7. rory

    This was great. It was an immense help for me to get flask up and running locally (on my mac) and on a remote dev server (linux)

    Reply
  8. Tomax

    I just finish it and worked very nicely. You have a really nice tutorial there! :)

    Just a minor repair in my opinion, I guess you could also tell that you are using the user flask with group www-data on the virtual host, I end up using www-data as the user also for this test.

    One nice upgrade would be, how to use it from outside the deploying machine lets say test it on another PC on the network, I think it would be good for the people out there giving the 1st steps.

    Reply
    1. Simon

      Hi Tomax, thank you for the nice words and for the feedback. www-data is just an apache convention, so it doesn’t strictly matter, however conventions are never a bad thing, so I’ll be updating this. Thanks.

      Accessing an Apache host from somewhere else on the network, isn’t any different for Flask, than it is for other frameworks or apache modules. However it’s something I’ll certainly consider writing about. Is it something you need help with?

      Reply
      1. Tomax

        No mate, that I actually know. Thank you a lot for this initial and nice help. It was just a suggestion for other people follow the tutorial If you still want to work on it. 😉

        Reply
          1. Peter Raeth

            Simon has done a superior job with this tutorial.

            How I do wish Tomax and Simon had said how to get access to my.webtool from some other machine on the network, and remotely through the internet.

            For instance, if I try http://192.168.1.159/my.webtool, that does not work. A 404, not found, error results. Not sure how else to proceed.

            If there is now a link to another article on this topic, I would be most grateful to hear about it.

            Thanks again for a clear and concise tutorial.

          2. Simon

            Hi Peter, sorry for my incredibly slow reply, the last few months life has really got on top of me, but I’m back now.

            Did you solve this problem? I can walk you through it on Skype or something, if you’re still wrestling with it.

          3. Filippo

            Hi, I have exactly the same problem as PETER RAETH. I found your tutorial very very helpful to create a flask app and reach to it from the computer and I succeeded in following all the steps. The only problem is that I would like to access to the app from another machine on the internet.

            Thank you so much again for your help,
            Filippo

          4. Simon

            Hi Filippo,

            I’m glad that you found this guide useful. Do you mean you are trying to run you Flask app in production? Or you’re just trying to access the app from another machine on the network?

          5. Matthias Jürges

            Hello Simon,
            i have the same problem as Filippo and Peter Raeth, i can’t access my application form an other machine on the intranet/network.

            I can access the webserver, but i couldn’t access my webtool…

            Do you have a tip for me?

          6. Simon

            Hi Matthias,

            Have you updated your hosts file on the client machine, to point my.webtool to the IP of the machine hosting the application?

            192.168.x.x my.webtool

            Replace “192.168.x.x” with private IP of machine serving the application.

  9. helge

    First, thank you for the really clear and short explanations.

    But I have one “Sorry!” for you, your script won’t run in production. Simply because it fails to append the necessary python-path for your webtool.py.

    I think it is irresponsible to use global python installations for anything that gets deployed. so virtualenv is a *must have*.

    Sure as long as you start things from the console, your webtool.py knows where the Flask-modules are located, assuming you did a “source bin/activate” from within your virtualenv folder.

    BUT in deployment, no one is on the console. Instead apache will trigger your webtool.py and it does not ave a clue where to find your Flask-modules you installed earlier.

    you really need to add following lines BEFORE everything else in your webtool.py:
    import sys
    sys.path.append("/var/www/flask-prod/lib/python2.7/site-packages")

    assuming you created that folder as a virtualenv which one should clearly do.

    just my 2 cents.

    Reply
    1. Simon

      Hi Helge, thank you for the nice word and even more so, for a great discussion point.

      Firstly I’d like to say that although this point never really come to mind when I write this, I’m glad it never. I suspect most Python developers are aware of virtualenv. I wanted this guide to be as concise as possible, setting all this up is a tedious process so the less fluff the better, if that makes sense. Otherwise the initial point gets lost in the blurb.

      Irrespective of that, it’s certainly an interesting point and depending on you infrastructure you do have a point. The web is evolving rapidly and with the likes of Docker and dirt cheap cloud services at our disposal, it’s rare to ever have two or more applications running on the same machine or container. I find using the likes of virtualenv to be over-engineering a problem, that isn’t really there.

      If someone out there is running multiple production applications on the same machine, it’s very likely they’re going to need to use your solution, so thank you for posting it, as I’m sure it will be useful.

      Cheers again

      Reply
  10. tom

    Hey Simon,

    thank you for this guide. I was kind of lost in the beginning because I had no idea about Apache, virtual hosts and so on.

    You helped me a lot and I just wanted to say one thing: thank you!

    Reply
  11. Nguyen

    Hello Simon,

    Thanks for your tutorial. It is really awesome!
    I am just confused a little bit regarding the usages of ” and “” in python.
    Could you explain the meaning of these signs as well as the usage of them to me?

    Thanks,

    Reply
    1. Simon

      Hi Nguyen,

      Thank you for you kind words, I’m really glad you found this guide useful. As for the quotes, these are just use to define strings in Python. Does that answer your question? Thanks

      Reply
      1. Nguyen

        Hallo Simon,

        Thanks for your reply. It answers my question, but just a half. I guess my question is not clear, and I am quite sure it makes you confused :)

        I’d like to ask about the difference between the single quote, ‘-‘, and the double quote, “-“, in python. When should we use ‘-‘ and when should we use “-“.

        Thanks

        Reply
  12. Basak

    Hello,

    Thank you very much for the tutorial. I have been struggling with web development using Flask. This one seems to be the most clear one. I followed every step easily. However, I still have some questions because it does not work for some reason, although I tried many times. I would be more than happy if you can help.

    I am developing a remote controlled pet feeder. I want to create a website to stream video from the camera on Pi and dispense food with the button on this website.

    – Project folder: /var/www/PetFeeder

    – Python script: /var/www/PetFeeder/PetFeeder.py
    *My python script calls other scripts (for camera etc.) which are again located under the folder ‘PetFeeder’. When I run this script through Terminal it starts publishing my html and buttons etc. works just fine. However it is only on the IP address of the Pi on port :5000. No access from outside of the local network.

    – Html file for my web page:/var/www/PetFeeder/templates/index.html

    – WSGI file: /var/www/PetFeeder/PetFeeder.wsgi

    – Conf. file: /etc/apache2/sites-available/my.PetFeeder.conf
    * 127.0.0.1 in the /etc/hosts was already in the file written ‘localhost’ in front of it. So, I just changed it to my.PetFeeder.

    – Content of /var/www/PetFeeder/PetFeeder.wsgi:

    import sys
    sys.path.append(‘/var/www/PetFeeder’)
    from PetFeeder import app as application

    – Content of /etc/apache2/sites-available/my.PetFeeder.conf:

    ServerName my.PetFeeder
    WSGIDaemonProcess PetFeeder user=www-data group=www-data threads=5 home=/var/www/PetFeeder/
    WSGIScriptAlias / /var/www/PetFeeder/PetFeeder.wsgi
    WSGIProcessGroup PetFeeder
    WSGIApplicationGroup %{GLOBAL}
    WSGIScriptReloading On
    Order deny,allow
    Allow from all

    *a2ensite my.PetFeeder returns ‘Site .. already enabled’
    *.. restart successfully returns [ok] Restarting apache2 ..

    I could not reach my page through any of the following:

    http://my.PetFeeder/
    http://IP_of_Pi/my.PetFeeder

    Reply
    1. Simon

      Hi there, glad you found the tutorial useful. Have you updated your hosts file with:

      127.0.0.1 my.PetFeeder

      Reply
      1. Basak

        Yes, actuallly 127.0.0.1 in the /etc/hosts was already in the file written ‘localhost’ in front of it. So, I just changed it to my.PetFeeder.

        Reply
          1. Basak

            Actually that was the action I have already done as I wrote in my original post. I just repeated it to emphasise that I have already done that. So the problem is still there.

  13. John

    Hey Simon – great tutorial! The WSGI module is now far less intimidating. I especially like the comment “Don’t rush it, this does work.” – I misspelled a file and resolved this on second run through. Thanks for sharing.

    Reply
    1. Simon

      Hey John, great to hear this worked for you. I always found the small subtle details to be the parts I made a mistake on too. There is a lot to be said for taking your time. Have fun with Flask!

      Reply
  14. Xilon

    Hey Simon! I’ve found some very similar tutorials around the web, but yours is the only one which cares to explain to details what each step is really doing, so thanks bro. However, it still couldn’t answer one question, so I’ll ask here:
    What if the machine I’m trying to deploy is a production amazon ec2 instance running ubuntu 16.04, how should be configured the host file so my web server could be accessed via internet? I’m asking it because I don’t know if the “127.0.0.1 my.webtool” would be enough to allow it? Thanks!

    Reply
    1. Simon

      Hey Xilon,

      Sorry for such for the belated reply. I need a new way to block spam, as I’m missing real comments.

      For your answer, once the app is hosted on ec2, you’ll be provided with a public IP. First, you have to make sure the IP is static, AWS call these Elastic IPs. Then you have to update your DNS record so that you have an A record for your domain and it’s pointing to your EC2 Elastic IP. Unless of course, you are using a load balancer, in which case the A record should be pointing at the load balancer. There is no need to update the hosts file on a production environment.

      I hope that helps.

      Reply
  15. Zach

    I followed the tutorial on a server that I have used ssh to access over vpn. The server has an ip address, but no domain name. How would I go about viewing the page to determine if I have followed the steps correctly?

    Reply
    1. Simon

      Hi Zach, if you just go to the IP address of the server, your application should load. Apache by default will select a virtualhost in alphabetical order, when one isn’t directly matched.

      Reply
  16. mingch

    Thanks for the great tutorial, I’ve one question, should it be removed from the following code when deploy into production?

    if __name__ == “__main__”:
    app.run(debug=True)

    Reply

Leave a Reply

Your email address will not be published.