Ubuntu 16.04 and Python 3.6+

I was recently in the process of upgrading a system running Django 2.2 to 3.2. Django 3.2 requires Python 3.6+. This system is running on a cloud instance of Ubuntu 16.04, which only supports Python up to 3.5.

Upgrading the server to Ubuntu 18.04 or 20.04 is out of the question. (Just the thought of someone typing do-release-upgrade in a production server makes me shiver…) Also, the system is from a pre-docker era, so no luck there.

The cleanest way to run a more recent Python version is to install pyenv. pyenv is a Python version management system which takes care of downloading, compiling, and installing the Python version you need without messing with the system configuration.

Python now requires OpenSSL version 1.1.1+, and Ubuntu 16.04 only provides OpenSSL up to version 1.0. So, if you try to install, for example, Python 3.10.0 using pyenv, you’ll get an error complaining about OpenSSL. (The error is actually silent. You’ll need to run pyenv with the verbose/-v in order to see the output.)

$ pyenv install -v 3.10.0

(Several lines and minutes later...)
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?


To fix this:

Install OpenSSL 1.1.1 from source:

Compile OpenSSL and install it.

   $ wget https://www.openssl.org/source/openssl-1.1.1l.tar.gz
   $ tar xvfz openssl-1.1.1l.tar.gz
   $ cd openssl-1-1-1l
   $ ./config
   $ make
   $ sudo make install

Add /usr/local/lib and /usr/local/local/lib64 to your library path in .bashrc (or whatever your system uses) and reload your shell using $ exec $SHELL.

export LD_LIBRARY_PATH=/usr/local/lib/:/usr/local/lib64:$LD_LIBRARY_PATH

Check that OpenSSL installed OK:

   $ openssl version

   OpenSSL 1.1.1l  24 Aug 2021

Tell pyenv where are the OpenSSL files located so the compiler and liker can find them and then run pyenv to install the desired Python version.

$ export LDFLAGS="-L/usr/local/lib/ -L/usr/local/lib64/"
$ export CPPFLAGS="-I/usr/local/include -I/usr/local/include/openssl"
$ export PYTHON_CONFIGURE_OPTS="--enable-shared"
$ pyenv install -v 3.10.0

You should have now a working Python 3.10.0 that you can use with pyenv.