系列文章:
1.在 Ubuntu 26.04 建立 Django 開發環境
2.Ubuntu 26.04:Apache + Django + MySQL 正式部署
以 Apache 2.4、mod_wsgi、Python 虛擬環境及 MySQL 8.4 部署 Django。這是 Django 官方文件所支援的正式部署方式。
一、部署架構
部署原則:
Apache 直接透過
mod_wsgi執行 Django,不使用開發伺服器。MySQL 只監聽本機,不對外公開 3306。
Django 專案由專用的系統帳號管理,Apache worker 不使用 root 權限。
HTTPS 使用 Let's Encrypt 憑證。
機密資料與程式碼分開保存,並定期備份資料庫。
二、安裝 Apache、Python 與 MySQL
1.更新系統:
$sudo apt update
$sudo apt upgrade -y
2.安裝必要套件:
$sudo apt install -y apache2 libapache2-mod-wsgi-py3 mysql-server \
python3 python3-venv python3-dev build-essential \
default-libmysqlclient-dev pkg-config libffi-dev libssl-dev \
git curl certbot python3-certbot-apache
3.啟動服務並設定開機自動啟動:
$sudo systemctl enable --now apache2
$sudo systemctl enable --now mysql
4.確認安裝:$python3 --version
$python3 --version
$apache2 -v
$mysql --version
$sudo apachectl -M | grep wsgi
$sudo systemctl is-active apache2
$sudo systemctl is-active mysql 5.注意:Ubuntu 26.04 的套件版本可能隨安全更新變動。這套做法使用 Ubuntu 套件庫提
供的
mod_wsgi,因此 Python 虛擬環境應使用相同的系統 Python 版本,避免 ABI 不相 容。
三、建立專用 Django 系統帳號與目錄
1.建立專用帳號:
$sudo adduser --system --group --home /srv/djangoapp \ --no-create-home djangoapp
$sudo mkdir -p /srv/djangoapp
$sudo chown djangoapp:djangoapp /srv/djangoapp
$sudo chmod 750 /srv/djangoapp
2.建立 Python 虛擬環境:
$sudo -u djangoapp python3 -m venv /srv/djangoapp/venv
3.安裝 Django 與 MySQL 驅動:
$sudo -u djangoapp /srv/djangoapp/venv/bin/python -m pip install \
--upgrade pip
$sudo -u djangoapp /srv/djangoapp/venv/bin/pip install \ "Django>=5.2,<5.3" "mysqlclient>=2.2.1,<3.0" \
"python-dotenv>=1.0,<2.0"
這裡固定在 Django 5.2 LTS 系列,避免未經測試就跨越主要版本。正式上線前,仍應依照專案相容性測試及安全更新政策選定確切套件版本。
4.確認版本:
$sudo -u djangoapp /srv/djangoapp/venv/bin/python -m django \
--version
$sudo -u djangoapp /srv/djangoapp/venv/bin/python -c \'import MySQLdb; print("MySQL driver OK")'
四、建立 MySQL 資料庫與專用帳號
1.執行 MySQL 安全設定:依照提示設定適合的安全選項。
$sudo mysql_secure_installation
2.若採用 Ubuntu 常見的本機 socket 管理方式,可先用下列指令登入:
$sudo mysql
3.在 MySQL 提示符中建立資料庫及專用使用者:
CREATE DATABASE django_db CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
4.產生一組隨機資料庫密碼:
複製產生的值,將下方 SQL 的請替換成隨機密碼 換成它:
CREATE USER 'django_user'@'localhost' IDENTIFIED BY '請替換成隨機密碼'; GRANT ALL PRIVILEGES ON django_db.* TO 'django_user'@'localhost';
EXIT;
此帳號只對 django_db 資料庫擁有權限,不使用 MySQL root 帳號執行 Django。GRANT ALL PRIVILEGES ON django_db.* 允許 Django 執行資料表建立與遷移等操作,適合這份單一專案範例。如果正式營運政策要求最小權限,應將部署遷移帳號與日常執行帳號分開。
5.確認 MySQL 沒有對公網監聽:
$sudo ss -lntp | grep 3306
一般本機部署應只顯示 127.0.0.1:3306 或 Unix socket,而不應顯示0.0.0.0:3306 或公網 IP。
五、建立 Django 專案
1.建立專案:
$sudo -u djangoapp /srv/djangoapp/venv/bin/django-admin \ startproject config /srv/djangoapp
2.專案目錄如下:
/srv/djangoapp/
├── manage.py
├── config/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
└── venv/
3.建立機密設定檔:
$sudo install -o root -g djangoapp -m 640 /dev/null /etc/djangoapp.env
4.產生 Django SECRET_KEY:
$openssl rand -base64 48
5.編輯設定檔:
$sudo nano /etc/djangoapp.env
6.填入以下內容,並將密碼及金鑰替換成你實際產生的值:
DJANGO_SECRET_KEY=替換成隨機金鑰
DJANGO_DEBUG=0
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DJANGO_CSRF_TRUSTED_ORIGINS=https://example.com,https://www.example.com
MYSQL_DATABASE=django_db
MYSQL_USER=django_user
MYSQL_PASSWORD=替換成前面建立的MySQL密碼
將 example.com 換成自己的網域;如果不使用 www,可將它從設定中移除。
7.確認機密檔案權限:
$sudo chown root:djangoapp /etc/djangoapp.env
$sudo chmod 640 /etc/djangoapp.env
這樣一般使用者無法讀取機密檔案,而 Django 專用帳號可以讀取。請勿把這份檔案放入 Git 或網站公開目錄。
六、設定 Django 連接 MySQL 與正式環境安全選項
以下會直接建立一份適用於這個範例的 config/settings.py。它會從 /etc/djangoapp.env 讀取機密資料。
$sudo tee /srv/djangoapp/config/settings.py > /dev/null <<'PY'
import os
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv("/etc/djangoapp.env")
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = [
host.strip()
for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
if host.strip()
]
CSRF_TRUSTED_ORIGINS = [
origin.strip()
for origin in os.environ.get(
"DJANGO_CSRF_TRUSTED_ORIGINS", ""
).split(",")
if origin.strip()
]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ["MYSQL_DATABASE"],
"USER": os.environ["MYSQL_USER"],
"PASSWORD": os.environ["MYSQL_PASSWORD"],
"HOST": "localhost",
"PORT": "3306",
"OPTIONS": {
"charset": "utf8mb4",
},
"CONN_MAX_AGE": 60,
}
}
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": (
"django.contrib.auth.password_validation."
"UserAttributeSimilarityValidator"
)
},
{
"NAME": (
"django.contrib.auth.password_validation."
"MinimumLengthValidator"
)
},
{
"NAME": (
"django.contrib.auth.password_validation."
"CommonPasswordValidator"
)
},
{
"NAME": (
"django.contrib.auth.password_validation."
"NumericPasswordValidator"
)
},
]
LANGUAGE_CODE = "zh-hant"
TIME_ZONE = "Asia/Taipei"
USE_I18N = True
USE_TZ = True
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = Path("/var/lib/djangoapp/media")
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_REFERRER_POLICY = "same-origin"
PY
將 HOST = "localhost" 用於本機 MySQL。Linux 上的 MySQL 驅動通常會透過 Unix socket 連線;如果你的 MySQL 只接受 TCP,請將 HOST 改成 127.0.0.1,並確認帳號授權和 MySQL bind address 相符。
2.建立媒體檔案目錄:
$sudo mkdir -p /var/lib/djangoapp/media
$sudo chown djangoapp:djangoapp /var/lib/djangoapp/media
$sudo chmod 750 /var/lib/djangoapp/media
3.將靜態檔案目錄預先建立好:
$sudo mkdir -p /srv/djangoapp/staticfiles
$sudo chown -R djangoapp:djangoapp /srv/djangoapp
$sudo chmod 755 /srv/djangoapp
4.設定專案程式檔案的基本權限:
$sudo find /srv/djangoapp/config -type d -exec chmod 755 {} \;
$sudo find /srv/djangoapp/config -type f -exec chmod 644 {} \;
$sudo chmod 644 /srv/djangoapp/manage.py
七、設定 Django 首頁
建立一個簡單首頁,方便測試整個部署流程。
1.建立 config/views.py:
$sudo tee /srv/djangoapp/config/views.py > /dev/null <<'PY'
from django.http import HttpResponse
def home(request):
return HttpResponse(
"Django on Ubuntu 26.04 + Apache + MySQL is running.",
content_type="text/plain; charset=utf-8",
)
PY
2.修改 config/urls.py:
$sudo tee /srv/djangoapp/config/urls.py > /dev/null <<'PY'
from django.contrib import admin
from django.urls import path
from config.views import home
urlpatterns = [
path("", home, name="home"),
path("admin/", admin.site.urls),
]
PY
3.調整檔案權限:
$sudo chown djangoapp:djangoapp /srv/djangoapp/config/views.py \
/srv/djangoapp/config/urls.py
$sudo chmod 644 /srv/djangoapp/config/views.py \
/srv/djangoapp/config/urls.py
4.完成後,網站首頁預期會顯示:
Django on Ubuntu 26.04 + Apache + MySQL is running.

沒有留言:
張貼留言