Quickstart from source

This guide provides instructions for running your own certificate authority by installing django-ca from source. This method requires a lot of manual configuration and a lot of expert knowledge, but is a good choice if you use an exotic system or other options do not work for you for some reason. If you’re looking for a faster and easier option, you might consider using docker-compose.

Note

All commands below assume that you have a shell with superuser privileges.

This tutorial will give you a CA with

  • A root and intermediate CA.

  • A browsable admin interface, protected by TLS (using certificates signed by your CA).

  • Certificate revocation using CRLs and OCSP.

  • (Optional) ACMEv2 support (= get certificates using certbot).

Requirements

This guide assumes that you have moderate knowledge of running servers, installing software and how TLS certificates work.

The guide assumes you use a dedicated server to set up your certificate authority and does not account for potential conflicts with other software like ports, directories or container names.

A certificate authority needs plain HTTP for CRL and OCSP access and HTTPS for ACMEv2. Using standard ports are strongly recommended (clients might fail otherwise). If you do not need any of the mentioned features, you can use all other features from the command line and do not need a web server.

Setup DNS

Before you set up your certificate authority, you need a domain name that points to the server where you install django-ca. Since the domain is encoded in CA certificates, it cannot be easily changed later.

This guide assumes that ca.example.com is the name that points to the server where you are setting up your certificate authority.

Required software

To run django-ca, you need Python. You will also need at least a supported database and a web server (like NGINX or Apache) to serve static files.

In our guide, we are going to run PostgreSQL as a database, Redis as a cache and NGINX as a front-facing web server . Please refer to your operating system installation instructions for how to install the software on your own.

On Debian/Ubuntu, simply do:

root@host:~# apt update
root@host:~# apt install python3 python3-venv python3-dev \
>     gcc libpq-dev postgresql postgresql-client \
>     redis-server nginx uwsgi uwsgi-plugin-python3

Environment

To make the guide less error-prone, we export the domain name for your certificate authority to $HOSTNAME. In all commands below assume that you have set the environment variable like this:

root@host:~# export HOSTNAME=ca.example.com

Installation

With this guide, you will install django-ca to /opt/django-ca/, with your local configuration residing in /etc/django-ca/. You also need to create a system user to run the uWSGI application server and Celery task worker:

root@host:~# mkdir -p /opt/django-ca/src/ /etc/django-ca/
root@host:~# adduser --system --group --disabled-login --home=/opt/django-ca/home/ django-ca
root@host:~# adduser django-ca www-data

Get the source

You can clone django-ca from git or download an archive from GitHub. In the example below, we extract the source to /opt/django-ca/src/ and create a symlink without a version so that you can roll back to old versions during an update:

root@host:~# cd /opt/django-ca/src/
root@host:/opt/django-ca/src/# wget -O django-ca-1.27.0.tar.gz \
>    https://github.com/mathiasertl/django-ca/archive/refs/tags/1.27.0.tar.gz
root@host:/opt/django-ca/src/# tar xf django-ca-1.27.0.tar.gz
root@host:/opt/django-ca/src/# ln -s /opt/django-ca/src/django-ca-1.27.0 django-ca

Create a virtualenv

In our setup, we create a virtualenv to install the Python environment. Several tools building on virtualenv exist (e.g. pyenv or virtualenvwrapper) that you might want to try out.

Warning

Always run pip in a virtualenv or it will update system dependencies and break your system!

root@host:~# python3 -m venv /opt/django-ca/venv/
root@host:~# /opt/django-ca/venv/bin/pip install -U \
>    pip setuptools wheel
root@host:~# /opt/django-ca/venv/bin/pip install -U \
>    -e /opt/django-ca/src/django-ca[postgres,celery,redis,yaml]

Alternatively, you can also use a pinned set of requirements created at the time of release by replacing the last command with:

root@host:~# /opt/django-ca/venv/bin/pip install -U \
>     -r /opt/django-ca/src/django-ca/requirements-pinned.txt \
>     -e /opt/django-ca/src/django-ca

Both commands will install PostgreSQL support, but not install MySQL support. If you want to use MySQL, install the mysql extra.

PostgreSQL database

Create a PostgreSQL database and make sure to use a randomly generated password and keep it for later configuration:

root@host:~# openssl rand -base64 32
...
root@host:~# sudo -u postgres psql
postgres=# CREATE DATABASE django_ca;
CREATE DATABASE
postgres=# CREATE USER django_ca WITH ENCRYPTED PASSWORD 'random-password';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE django_ca TO django_ca;
GRANT

Add SystemD services

SystemD services are included with django-ca. You need to add three services, one for the uWSGI application server (django-ca), one for the Celery task worker (django-ca-celery) and one for the Celery task scheduler (django-ca-celerybeat):

root@host:~# ln -s /opt/django-ca/src/django-ca/systemd/systemd.conf /etc/django-ca/
root@host:~# ln -s /opt/django-ca/src/django-ca/systemd/*.service /etc/systemd/system/
root@host:~# systemctl daemon-reload
root@host:~# systemctl enable django-ca django-ca-celery django-ca-celerybeat

Note that the services will not yet start due to missing configuration.

If you use an installation directory other then /opt/django-ca, set INSTALL_BASE in /etc/systemd/systemd-local.conf (see SystemD configuration) and add a SystemD override for WorkingDirectory=.

Configuration

django-ca will load configuration from all *.yaml files in /etc/django-ca/ in alphabetical order. These files can contain any Django setting, Celery setting or django-ca setting.

If you (mostly) followed the above examples, you can symlink conf/source/00-settings.yaml to /etc/django-ca and just override a few settings in /etc/django-ca/10-localsettings.yaml. To create the symlink:

root@host:~# ln -s /opt/django-ca/src/django-ca/conf/source/00-settings.yaml /etc/django-ca/

And then simply create a minimal /etc/django-ca/10-localsettings.yaml - but you can override any other setting here as well:

/etc/django-ca/10-localsettings.yaml
# django-ca local settings file. For more information:
#   https://django-ca.readthedocs.io/en/latest/settings.html for more information.

# The hostname for your CA.
# WARNING: Changing this requires new CAs (because the hostname goes into the certificates).
CA_DEFAULT_HOSTNAME: "ca.example.com"

# The URL base path used for ACMEv2/OCSP/CRL URLs. If given, the path **must** end with a slash.
#
# If you're upgrading from a previous version and have existing CAs, uncomment or set to "django_ca/".
#
# WARNING: Changing this requires new CAs (because the path goes into the certificates).
DJANGO_CA_CA_URL_PATH: ""

# Secret key used by this installation. Generate e.g. with "openssl rand -base64 32".
SECRET_KEY: ""

# Database configuration
DATABASES:
    default:
        ENGINE: django.db.backends.postgresql
        HOST: localhost
        PORT: 5432
        NAME: django_ca
        USER: django_ca
        PASSWORD: random-password

Please see Custom settings for a list of available settings and especially YAML configuration for more YAML configuration examples.

SystemD configuration

When you added SystemD services you also created a symlink for /etc/django-ca/systemd.conf. If settings there do not suit you, you can override them in /etc/django-ca/systemd-local.conf.

Add manage.py shortcut

As optional convenience, you can create a symlink to a small wrapper script that allows you to easily run manage.py commands. In the examples below the guide assumes you created this symlink at /usr/local/bin/django-ca, but of course you can name the symlink anything you like:

root@host:~# ln -s /opt/django-ca/src/django-ca/conf/source/manage /usr/local/bin/django-ca
root@host:~# django-ca check
System check identified no issues (0 silenced).

Setup Database and static files

Populate the database and setup the static files directory:

root@host:~# django-ca migrate
root@host:~# FORCE_USER=root django-ca collectstatic

The collectstatic command needs to run as root.

Start

You can now finally start the uWSGI application server and the Celery worker (omit django-ca service if you do not intend to run a web server):

root@host:~# systemctl start django-ca django-ca-celery django-ca-celerybeat

Create admin user and set up CAs

Because we created a shortcut above above, we can use django-ca to use django-ca from the command line.

Custom management commands are documented in Command-line interface. You need to create a user (that can log into the admin interface) and create a root and intermediate CA:

root@host:~# django-ca createsuperuser
...
root@host:~# django-ca init_ca --path-length=1 --subject-format=rfc4514 Root "CN=Root"
root@host:~# django-ca init_ca  --acme-enable --parent="Root" --subject-format=rfc4514 \
>     Intermediate "CN=Intermediate"

There are a few things to break down in the above commands:

  • The subject (CN=...) in the CA is only used by browsers to display the name of a CA. It can be any human readable value and does not have to be a domain name.

  • The first positional argument to init_ca, (“Root”, “Intermediate”) is just a human readable name used to identify the CA within the command-line interface and web interface. Unlike the CommonName, it must be unique.

  • The --pathlen=1 parameter for the root CA means that there is at most one level of intermediate CAs.

Setup NGINX

A web server is required for the admin interface, certificate revocation status via OCSP or CRLs and ACMEv2 (the protocol used by Let’s Encrypt/certbot integration).

Warning

While theoretically possible, do not use a local CAs ACMEv2 interface to get certificates. Any misconfiguration might make it impossible to retrieve a certificate!

In this setup, we’ll create certificates using the CA we created above. If you want to use Let’s Encrypt certificates instead, you can have a look at our Quickstart with Docker Compose for an example.

Create a private/public key pair for NGINX to use:

root@host:~# openssl genrsa -out /etc/ssl/$HOSTNAME.key 4096
root@host:~# openssl req -new -key /etc/ssl/$HOSTNAME.key -out /tmp/ca.csr -utf8 -batch
root@host:~# django-ca sign_cert --ca=Intermediate --csr=/tmp/ca.csr --bundle --webserver --subject-format=rfc4514 --subject CN=$HOSTNAME \
>     > /etc/ssl/$HOSTNAME.pem

Create DH parameters:

root@host:~# mkdir -p /etc/nginx/dhparams/
root@host:~# openssl dhparam -dsaparam -out /etc/nginx/dhparams/dhparam.pem 4096

django-ca includes a template for envsubst(1) that you can use. The template assumes that you have set $HOSTNAME:

root@host:~# envsubst < /opt/django-ca/src/django-ca/nginx/source.template \
>     > /etc/nginx/sites-available/django-ca.conf
root@host:~# ln -fs /etc/nginx/sites-available/django-ca.conf /etc/nginx/sites-enabled/
root@host:~# nginx -t
root@host:~# systemctl restart nginx

Use your CAs

After adding your Root CA to your system, you can use the admin interface at https://ca.example.com/admin/ with the credentials you created above to create new certificates or revoke certificates.

CRL and OCSP services are provided by default, so there is nothing you need to do to enable them.

You can use the Command-line interface for creating new CAs as well as issuing, renewing and revoking certificates. The manage.py script is available via the django-ca symlink you created above, for example:

root@host:~# django-ca list_cas

Add CA to your system

To get the certificate for your Root CA, you can use the admin interface or the dump_ca command:

root@host:~# django-ca dump_ca Root > root.pem

You can add this file directly to the list of known CAs in your browser.

Distributions usually provide instructions for how to add a CA to the whole system, see for example these instructions for Debian/Ubuntu.

Use ACME with certbot

If you enabled ACMEv2 support, all you need to do is enable ACMEv2 for the intermediate CA using the admin interface (or using django-ca edit_ca). After that, you can retrieve a certificate using a simple certbot command:

$ sudo certbot register --server https://ca.example.com/acme/directory/
$ sudo certbot certonly --server https://ca.example.com/acme/directory/ ...

Update

When updating, first check the ChangeLog for any breaking changes. Under Update you’ll also find notes on any manual steps you might need to take.

Downloading the new release works the same as before, but you have to remove the old symlink before creating the new one:

root@host:~# cd /opt/django-ca/src/
root@host:/opt/django-ca/src/# ls -l
lrwxrwxrwx  ...   django-ca -> /opt/django-ca/src/django-ca-1.19.1
drwxrwxr-x  ...   django-ca-1.19.1
root@host:/opt/django-ca/src/# wget -O django-ca-1.27.0.tar.gz \
>    https://github.com/mathiasertl/django-ca/archive/refs/tags/1.27.0.tar.gz
root@host:/opt/django-ca/src/# tar xf django-ca-1.27.0.tar.gz
root@host:/opt/django-ca/src/# rm django-ca
root@host:/opt/django-ca/src/# ln -s /opt/django-ca/src/django-ca-1.27.0 django-ca
root@host:/opt/django-ca/src/# ls -l
lrwxrwxrwx  ...   django-ca -> /opt/django-ca/src/django-ca-1.19.2
drwxrwxr-x  ...   django-ca-1.19.1
drwxrwxr-x  ...   django-ca-1.19.2

Update the database schema and static files:

root@host:~# django-ca migrate
root@host:~# FORCE_USER=root django-ca collectstatic

Restart services:

root@host:~# systemctl restart django-ca django-ca-celery django-ca-celerybeat

Update the NGINX configuration:

root@host:~# envsubst < /opt/django-ca/src/django-ca/nginx/source.template \
>     < /opt/django-ca/src/django-ca/nginx/source.template \
>     > /etc/nginx/sites-available/django-ca.conf
root@host:~# nginx -t
root@host:~# systemctl restart nginx

Uninstall

To completely uninstall django-ca, stop related services and remove files that where created:

root@host:~# systemctl stop django-ca django-ca-celery django-ca-celerybeat
root@host:~# systemctl disable django-ca django-ca-celery django-ca-celerybeat
root@host:~# rm -f /etc/nginx/sites-*/django-ca.conf
root@host:~# rm -f /var/log/nginx/$HOSTNAME*.log
root@host:~# rm -f /usr/local/bin/django-ca
root@host:~# rm -rf /etc/django-ca/ /opt/django-ca/ /var/log/django-ca
root@host:~# rm -f /etc/ssl/$HOSTNAME.{key,pem}

Restart NGINX so that it no longer knows about the configurations:

root@host:~# systemctl restart nginx

Remove the system user:

root@host:~# deluser django-ca

Drop the PostgreSQL database:

root@host:~# sudo -u postgres psql
postgres=# DROP DATABASE django_ca;
DROP DATABASE
postgres=# DROP USER django_ca;
DROP ROLE