Tech Fundamentals

Setting Up MySQL for Django on Ubuntu (Production)

Set up MySQL for a Django app in production on Ubuntu: install the server, create a database and least-privilege user with utf8mb4, and connect it via mysqlclient.

Long Nguyen Avatar

Long Nguyen

Fullstack Developer · AI Engineer · Researcher

3 min read
Django connecting through the mysqlclient driver to a MySQL database with a dedicated least-privilege user

Why MySQL for Production

Django's default SQLite database is fine for development but not for a real deployment. MySQL is one of the most widely used production databases — stable, fast, and well supported. This guide sets up MySQL for Django on Ubuntu the production way: installing the server, securing it, creating a dedicated least-privilege user, and connecting Django through the recommended driver.

As with any production database, the guiding principle is least privilege: the app gets its own database and its own user scoped to exactly that database, nothing wider.

Installing and Securing MySQL

Install the MySQL server from Ubuntu's repositories:

sudo apt update
sudo apt install mysql-server -y

Then run the built-in hardening script. It walks you through setting a root password policy, removing anonymous users, disabling remote root login, and dropping the test database — all sensible defaults for a production server:

sudo mysql_secure_installation

Skipping this step is a common mistake: a fresh MySQL install ships with conveniences that are fine for a laptop and risky on a public server.

Creating the Database and a Least-Privilege User

Open the MySQL shell as root:

sudo mysql

Create the database with the modern utf8mb4 character set — this is important, and a frequent source of bugs if skipped. The older utf8 in MySQL is a misnomer that can't store all Unicode (including many emoji); utf8mb4 is true, full UTF-8:

CREATE DATABASE aiinterviewer CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Then create a dedicated user scoped to localhost, and grant it privileges on only that database — not the whole server:

CREATE USER 'aiintervieweruser'@'localhost' IDENTIFIED BY 'your-strong-password';

GRANT ALL PRIVILEGES ON aiinterviewer.* TO 'aiintervieweruser'@'localhost';

FLUSH PRIVILEGES;
EXIT;

The scoping matters twice over: aiinterviewer.* limits the user to this one database, and @'localhost' means the account can only connect from the server itself, not remotely. FLUSH PRIVILEGES reloads the grant tables so the changes take effect.

Installing the Database Driver

Django connects to MySQL through mysqlclient, the driver its own documentation recommends. Unlike a pure-Python driver, mysqlclient compiles against system libraries, so install those build dependencies first:

sudo apt install python3-dev default-libmysqlclient-dev build-essential pkg-config -y

Then install the driver into your virtual environment:

pip install mysqlclient

That two-step order is the fix for the most common MySQL-with-Django error — a failed mysqlclient build. It fails when the system headers aren't present; installing them first makes the compile succeed.

Connecting Django

Point Django at the database, reading credentials from a .env file rather than hard-coding them:

# settings.py
from decouple import config

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": config("DB_NAME"),
        "USER": config("DB_USER"),
        "PASSWORD": config("DB_PASSWORD"),
        "HOST": config("DB_HOST", default="127.0.0.1"),
        "PORT": config("DB_PORT", default="3306"),
        "OPTIONS": {
            "charset": "utf8mb4",
            "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    }
}

Two options here earn their place. charset: utf8mb4 matches the database so the whole stack speaks full UTF-8 end to end. And STRICT_TRANS_TABLES makes MySQL reject invalid data with an error instead of silently truncating it — you want a loud failure, not quietly corrupted rows. Using 127.0.0.1 rather than localhost also forces a predictable TCP connection.

Finally, run your migrations:

python manage.py migrate

If it completes cleanly, MySQL is wired into Django and ready for production traffic.

Prefer PostgreSQL for this project instead? The same production setup for PostgreSQL — install, a least-privilege user, and a locked-down schema — is covered in Setting Up PostgreSQL for Django on Ubuntu. And with your database ready, the next step is deployment: see Deploying a Django App to Production With Gunicorn & Nginx.

FAQ

Frequently asked questions

Why use utf8mb4 instead of utf8 in MySQL?

MySQL's 'utf8' is a historical misnomer that only stores up to 3 bytes per character and can't represent all Unicode, including many emoji. utf8mb4 is true, full 4-byte UTF-8 — always use it for new databases to avoid encoding bugs.

Why does installing mysqlclient fail, and how do I fix it?

mysqlclient compiles against system libraries, so it fails when the development headers are missing. Installing python3-dev, default-libmysqlclient-dev, build-essential, and pkg-config first gives the compiler what it needs, and the pip install then succeeds.

Why scope the user to a single database and localhost?

Granting privileges on only aiinterviewer.* limits the account to that one database, and @'localhost' stops it connecting remotely. Together they follow least privilege, so a compromised app account can't reach other databases or be used from off the server.

What does STRICT_TRANS_TABLES do in the Django settings?

It makes MySQL raise an error on invalid or out-of-range data instead of silently truncating it. A loud failure during development is far better than discovering quietly corrupted data in production later.

Stay updated with Netalith

Get coding resources, product updates, and special offers directly in your inbox.