Instantly share code, notes, and snippets.

Embed
What would you like to do?
Setting environment variables for Apache/mod_wsgi hosted Python application.

Django documentation says to use:

WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com

<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>

To set environment variables which would be inherited by the Django application referred to by this, one can set them in the envvars file created as part of the Apache installation. There are a few problems with that though.

The first is that Linux distributions created a completely different way of configuring Apache and ignored in large part how the Apache Software Foundations default Apache distribution works. How you would do the equivalent varies between Linux distributions but generally requires setting some other special file alongside the system init scripts. Unfortunately some Linux distributions didn't make this a separate file and instead require you to modify the init scripts themselves.

The second is that the envvars file applies to the whole of Apache and cannot be made to apply to specific mod_wsgi daemon processes. This makes it unsuitable where hosting multiple Python web applications under the same Apache.

The much simpler way is to create a separate WSGI script file independent of the wsgi.py that Django generates with its startproject template. You might call this django.wsgi, or you might have separate production.wsgi and development.wsgi files if have multiple environments. Place the WSGI script file in the same directory as the wsgi.py file. In the file add something like:

import os

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

os.environ['DJANGO_CONFIGURATION'] = 'Production'
os.environ['DJANGO_SECRET_KEY'] = 'RANDOM_SECRET_KEY_HERE'
os.environ['DJANGO_AWS_ACCESS_KEY_ID'] = 'YOUR_AWS_ID_HERE'
os.environ['DJANGO_AWS_SECRET_ACCESS_KEY'] = 'YOUR_AWS_SECRET_ACCESS_KEY_HERE'
os.environ['DJANGO_AWS_STORAGE_BUCKET_NAME'] = 'YOUR_AWS_S3_BUCKET_NAME_HERE'
os.environ['SENDGRID_USERNAME'] = 'YOUR_SENDGRID_USERNAME'
os.environ['SENDGRID_PASSWORD'] = 'YOUR_SENDGRID_PASSWORD'

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

If you are concerned that you might have sensitive settings in a file which is part of your source code repository. Then have just the environment variables being set in a separate Python code file which you manually copy into place on the host in a directory such as /etc.

For example, on OpenShift, you would place the file in ${OPENSHIFT_DATA_DIR} and then use:

import os

OPENSHIFT_DATA_DIR = os.environ['OPENSHIFT_DATA_DIR']

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

execfile(os.path.join(OPENSHIFT_DATA_DIR, 'mysite-production-envvars.py'))

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Alternatively, you could put the WSGI script file in a completely different location outside of the source code for your application, even putting it in a subdirectory under /etc. In other words, tell your system adminstrator what needs to go in it and let them create it where they want to per their policies.

With the modified WSGI script file in place, change the name of the WSGI script file in the Apache configuration snippet to refer to it instead of the original wsgi.py.

WSGIScriptAlias / /path/to/mysite.com/mysite/production.wsgi
WSGIPythonPath /path/to/mysite.com

<Directory /path/to/mysite.com/mysite>
<Files production.wsgi>
Require all granted
</Files>
</Directory>

Change the WSGIScriptAlias and Directory directive paths if you do put it under /etc somewhere, but leave the WSGIPythonPath setting alone as that still needs to refer to where the source code for the project resides.

If using mod_wsgi daemon mode as you should be, you would instead also have something like:

WSGIDaemonProcess example.com python-path=/path/to/mysite.com:/path/to/venv/lib/python2.7/site-packages
WSGIProcessGroup example.com

So you would be setting the python-path option there to the location of your your source code instead. If using a modern version of mod_wsgi, use python-home instead.

This method can be used for any environment variables with the exception of those which affect the operation of the processes as a whole at a system level. That is, you cannot set LANG and LC_ALL in this way. Modern versions of mod_wsgi though have lang and locale options on WSGIDaemonProcess directive which achieve the same result. If not using daemon mode, you should be as embedded mode isn't generally recommended.

The only other problematic environment variable is LD_LIBRARY_PATH. This is only a problem where shared libraries aren't installed in a proper system location. If this is unavoidable, set LD_RUN_PATH to the library directory when installing any Python modules using the library and the location will be embedded in the Python module extension so that the shared library will be found whether or not LD_LIBRARY_PATH is set properly.

In summary, ignore the Django generated wsgi.py file. This never used to exist and that it started to be generated has caused problems with understanding how to set up Apache/mod_wsgi ever since. The first problem was that it used os.environ.setdefault() which causes problems when running multiple Django applications in the same Apache process. Secondly, that wsgi.py was auto generated, it gave users the impression that it would always be correct, must be used and wouldn't need modifying. Consequence was that people stopped seeing what one of the main roles of the WSGi script file was for, which was to set environment variables before the Django application got loaded.

@TwelveNights

This comment has been minimized.

TwelveNights commented Feb 13, 2017

Could you not keep everything in just one wsgi file?

@cowbert

This comment has been minimized.

cowbert commented Dec 14, 2017

@TwelveNights Yes, although you may not want to (for example, if you don't want to still make sure manage.py will remain portable in debugging mode and not use the same environment that the .wsgi file called by WSGIScriptAlias sets up).

@jlyons18

This comment has been minimized.

jlyons18 commented Jun 15, 2018

Just the solution I needed, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment