uWSGI with cookiecutter Pyramid application Part 1: Basic uWSGI + nginxΒΆ

uWSGI is a software application for building hosting services. It is named after the Web Server Gateway Interface (the WSGI specification to which many Python web frameworks conform).

This guide will outline broad steps that can be used to get a cookiecutter Pyramid application running under uWSGI and nginx. This particular tutorial was developed and tested on Ubuntu 18.04, but the instructions should be largely the same for all systems, where you may adjust specific path information for commands and files.

Note

For those of you with your hearts set on running your Pyramid application under uWSGI, this is your guide.

However, if you are simply looking for a decent-performing production-grade server with auto-start capability, Waitress + systemd has a much gentler learning curve.

With that said, let's begin.

  1. Install prerequisites.

    $ sudo apt install -y uwsgi-core uwsgi-plugin-python3 python3-cookiecutter \
                          python3-pip python3-venv nginx
    
  2. Create a Pyramid application. For this tutorial we'll use the starter cookiecutter. See Creating a Pyramid Project for more in-depth information about creating a new project.

    $ cd ~
    $ python3 -m cookiecutter gh:Pylons/pyramid-cookiecutter-starter
    

    If prompted for the first item, accept the default yes by hitting return.

    You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
    Is it okay to delete and re-clone it? [yes]: yes
    project_name [Pyramid Scaffold]: myproject
    repo_name [myproject]: myproject
    Select template_language:
    1 - jinja2
    2 - chameleon
    3 - mako
    Choose from 1, 2, 3 [1]: 1
    
  3. Create a virtual environment which we'll use to install our application.

    $ cd myproject
    $ python3 -m venv env
    
  4. Install your Pyramid application and its dependencies.

    $ env/bin/pip install -e ".[testing]"
    
  5. Create a new directory at ~/myproject/tmp to house a pidfile and a unix socket. However, you'll need to make sure that two users have access to change into the ~/myproject/tmp directory: your current user (mine is ubuntu), and the user that nginx will run as (often named www-data or nginx).

  6. Add a [uwsgi] section to production.ini. Here are the lines to include:

    [uwsgi]
    proj = myproject
    chdir = /home/ubuntu/%(proj)
    processes = 2
    threads = 2
    offload-threads = 2
    stats =  127.0.0.1:9191
    max-requests = 5000
    master = True
    vacuum = True
    enable-threads = true
    harakiri = 60
    chmod-socket = 020
    plugin = python3
    pidfile=%(chdir)/tmp/%(proj).pid
    socket = %(chdir)/tmp/%(proj).sock
    virtualenv = %(chdir)/env
    uid = ubuntu
    gid = www-data
    # Uncomment `wsgi-file`, `callable`, and `logto` during Part 2 of this tutorial
    #wsgi-file = wsgi.py
    #callable = app
    #logto = /var/log/uwsgi/%(proj).log
    

    And here is an explanation of the salient options:

    # Explanation of Options
    #
    # proj = myproject                    # Set a variable named "proj"
    #                                       so we can use it elsewhere in this
    #                                       block of config.
    #
    # chmod-socket = 020                  # Change permissions on socket to
    #                                       at least 020 so that, in combination
    #                                       with "--gid www-data", nginx will be able
    #                                       to write to it after uWSGI creates it.
    #
    # enable-threads                      # Execute threads that are in your app
    #
    # plugin = python3                    # Use the python3 plugin
    #
    # socket = %(chdir)/tmp/%(proj).sock  # Where to put the unix socket
    # pidfile=%(chdir)/tmp/%(proj).pid    # Where to put PID file
    #
    # uid = ubuntu                        # Masquerade as the ubuntu user.
    #                                       This grants you permissions to use
    #                                       python packages installed in your
    #                                       home directory.
    #
    # gid = www-data                      # Masquerade as the www-data group.
    #                                       This makes it easy to allow nginx
    #                                       (which runs as the www-data group)
    #                                       access to the socket file.
    #
    # virtualenv = (chdir)/env            # Use packages installed in your
    #                                       virtual environment.
    
  7. Invoke uWSGI with --ini-paste-logged.

    There are multiple ways to invoke uWSGI. Using --ini-paste-logged is the easiest, as it does not require an explicit entry point.

    $ cd ~/myproject
    $ sudo uwsgi --plugin python3 --ini-paste-logged production.ini
    
    # Explanation of Options
    #
    # sudo uwsgi                          # Invoke as sudo so you can masquerade
    #                                       as the users specfied by ``uid`` and
    #                                       ``gid``
    #
    # --plugin=python3                    # Use the python3 plugin
    #
    # --ini-paste-logged                  # Implicitly defines a wsgi entry point
    #                                       so that you don't have to.
    #                                       Also enables logging.
    
  8. Verify that the output of the previous step includes a line that looks approximately like this:

    WSGI app 0 (mountpoint='/') ready in 1 seconds on interpreter 0x5615894a69a0 pid: 8827 (default app)
    

    If any errors occurred, you will need to correct them. If you get a uwsgi: unrecognized option '--ini-paste-logged', make sure you are specifying the python3 plugin.

    If you get an error like this:

    Fatal Python error: Py_Initialize: Unable to get the locale encoding
    ModuleNotFoundError: No module named 'encodings'
    

    check that the virtualenv option in the [uwsgi] section of your .ini file points to the correct directory. Specifically, it should end in env, not bin.

    For any other import errors, it probably means that the package either is not installed or is not accessible by the user. That's why we chose to masquerade as the normal user that you log in as, so you would for sure have access to installed packages.

    If you get almost no output at all, yet the process still appears to be running, make sure that logto is commented out in production.ini.

  9. Add a new file at /etc/nginx/sites-enabled/myproject.conf with the following contents. Also change any occurrences of the word ubuntu to your actual username.

    server{
      server_name _;
    
      root /home/ubuntu/myproject/;
    
      location /  {
        include uwsgi_params;
        # The socket location must match that used by uWSGI
        uwsgi_pass unix:/home/ubuntu/myproject/tmp/myproject.sock;
      }
    }
    
  10. If there is a file at /var/nginx/sites-enabled/default, remove it so your new nginx config file will catch all traffic. (If default is in use and important, simply add a real server_name to /etc/nginx/sites-enabled/myproject.conf to disambiguate them.)

  11. Reload nginx.

    $ sudo nginx -s reload
    
  12. Visit http://localhost in a browser. Alternatively call curl localhost from a terminal. You should see the sample application rendered.

  13. If the application does not render, tail the nginx logs, then refresh the browser window (or call curl localhost) again to determine the cause. (uWSGI should still be running in a separate terminal window.)

    $ cd /var/log/nginx
    $ tail -f error.log access.log
    

    If you see a No such file or directory error in the nginx error log, verify the name of the socket file specified in /etc/nginx/sites-enabled/myproject.conf. Verify that the file referenced there actually exists. If it does not, check what location is specified for socket in your .ini file, and verify that the specified file actually exists. Once both uWSGI and nginx both point to the same file and both have access to its containing directory, you will be past this error. If all else fails, put your sockets somewhere writable by all, such as /tmp.

    If you see an upstream prematurely closed connection while reading response header from upstream error in the nginx error log, something is wrong with your application or the way uWSGI is calling it. Check the output from the window where uWSGI is still running to see what error messages it gives when you curl localhost.

    If you see a Connection refused error in the nginx error log, check the permissions on the socket file that nginx says it is attempting to connect to. The socket file is expected to be owned by the user ubuntu and the group www-data because those are the uid and gid options we specified in the .ini file. If the socket file is owned by a different user or group than these, correct the uWSGI parameters in your .ini file.

    If you are still getting a Connection refused error in the nginx error log, check permissions on the socket file. Permissions are expected to be 020 as set by your .ini file. The 2 in the middle of 020 means group-writable, which is required because uWSGI first creates the socket file, then nginx (running as the group www-data) must have write permissions to it or it will not be able to connect. You can use permissions more open than 020, but in testing this tutorial 020 was all that was required.

  14. Once your application is accessible via nginx, you have cause to celebrate.

    If you wish to also add the uWSGI Emperor and systemd to the mix, proceed to part 2 of this tutorial: uWSGI with cookiecutter Pyramid Application Part 2: Adding Emperor and systemd.

uWSGI has many knobs and a great variety of deployment modes. This is just one representation of how you might use it to serve up a cookiecutter Pyramid application. See the uWSGI documentation for more in-depth configuration information.

This tutorial is modified from the original tutorial Running a Pyramid Application under mod_wsgi.