1 12/04/2026 11 min

CentOS 7 e Django: scelta dello stack prima dei comandi

Su CentOS 7 il punto critico non è Django in sé, ma il contorno: Python di sistema è vecchio, i repository standard sono conservativi e il ciclo di vita della distribuzione impone qualche attenzione in più su versioni, dipendenze e servizi. La strada più pulita è tenere Django isolato in un virtual environment, lasciare il Python di sistema intatto e pubblicare l’app tramite un application server come Gunicorn dietro Apache o Nginx.

Se l’obiettivo è un’installazione robusta e manutenibile, evita di installare pacchetti Python globalmente con sudo pip. Su una macchina condivisa o in produzione ti complica subito gli aggiornamenti, e su CentOS 7 rischi di sporcare librerie usate da altri servizi. La combinazione più lineare è: Python 3 da repository affidabile, ambiente virtuale per il progetto, database separato, servizio systemd per l’avvio automatico e reverse proxy davanti all’app.

Prerequisiti minimi da verificare

Prima di toccare il progetto, controlla tre cose: accesso root o sudo, connettività verso i repository, e spazio disco sufficiente per pacchetti, build e log. Se manca anche solo uno di questi elementi, l’installazione si ferma a metà e poi si perde tempo a inseguire errori secondari.

Verifica la versione del sistema e la presenza di tool base:

cat /etc/centos-release
uname -r
sudo yum -y install epel-release
sudo yum -y install wget curl git policycoreutils-python

Il pacchetto policycoreutils-python serve spesso quando SELinux è attivo e devi sistemare contesti o porte per il servizio web. Non è sempre necessario, ma averlo pronto evita di fermarsi quando il proxy non riesce a parlare con l’app.

Installare Python 3 senza rompere il Python di sistema

CentOS 7 nasce con Python 2 come base di molte utility di sistema. Per Django moderno serve Python 3, quindi la scelta corretta è installarlo in parallelo. In ambienti moderni si usa spesso IUS, SCL o un repository aziendale controllato; qui l’obiettivo pratico è ottenere un interprete recente e stabile senza sostituire quello di default.

Un approccio comune è usare yum con i repository EPEL e un modulo Python 3 disponibile nel tuo standard interno. Se hai già un repo affidabile, installa il pacchetto Python 3 corrispondente. Esempio:

sudo yum -y install python36 python36-devel python36-pip

Se il nome del pacchetto cambia nel tuo repository, il principio non cambia: devi avere python3, pip3 e i pacchetti di sviluppo per compilare dipendenze native. Dopo l’installazione, controlla subito la versione reale del binario che userai nel progetto:

python3.6 --version
pip3.6 --version

Se il comando non esiste, non improvvisare con symlink casuali: individua il binario corretto con rpm -ql o which e usa quello nelle istruzioni successive.

Creare l’utente applicativo e la directory del progetto

Il progetto non va eseguito come root. Crea un utente dedicato, una home ordinata e una directory applicativa sotto controllo. Questo riduce il blast radius se un processo web viene compromesso e semplifica backup e permessi.

sudo useradd --system --create-home --shell /bin/bash djangoapp
sudo mkdir -p /srv/django/miosito
sudo chown -R djangoapp:djangoapp /srv/django

Se preferisci una struttura diversa, mantieni almeno questa separazione: codice in una directory dedicata, dati persistenti fuori dal tree del repository, e file di configurazione del servizio in /etc. Non mescolare tutto nella home dell’utente se prevedi deployment ripetibili.

Virtual environment e installazione di Django

Il virtual environment è il confine operativo del progetto. Dentro ci stanno Django e le dipendenze, fuori resta il sistema. È il modo più semplice per aggiornare senza impattare altri servizi e per ricreare l’ambiente su un’altra macchina.

sudo -iu djangoapp
cd /srv/django/miosito
python3.6 -m venv venv
source venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install Django

Se hai già deciso una versione specifica, fissala esplicitamente. In produzione è meglio non lasciare il minor release aperto:

pip install 'Django==4.2.*'

Dopo l’installazione, verifica che il modulo importi correttamente e che la versione sia quella attesa:

python -c "import django; print(django.get_version())"

Se questo comando fallisce, il problema è quasi sempre nel virtualenv attivo, nel binario Python sbagliato o in una dipendenza che non si è installata. Non andare avanti con il resto finché questo test non è pulito.

Creare il progetto Django e validare l’avvio locale

Una volta installato Django, genera il progetto e fai un test locale prima di introdurre web server e proxy. È il check più economico per capire se l’ambiente base funziona.

django-admin startproject config .
python manage.py migrate
python manage.py runserver 0.0.0.0:8000

Apri la porta 8000 solo per il test interno o in una rete controllata. Se il comando runserver parte e la pagina di default risponde, hai già validato Python, Django, le dipendenze minime e la struttura del progetto.

Se stai lavorando su una macchina remota, un controllo rapido è questo:

curl -I http://127.0.0.1:8000/

L’esito atteso è un HTTP/1.1 200 OK o comunque una risposta coerente con l’app in avvio. Se ricevi errore di connessione, torna indietro e controlla il processo, la porta e l’attivazione del virtualenv.

Configurare settings.py per produzione senza scorciatoie

La configurazione di default non è adatta alla produzione. Devi impostare almeno DEBUG = False, definire ALLOWED_HOSTS, scegliere una chiave segreta sicura e separare i parametri sensibili dal codice. La regola pratica è semplice: il repository non deve contenere segreti in chiaro.

Nel file config/settings.py o in un modulo dedicato, imposta i valori essenziali:

DEBUG = False
ALLOWED_HOSTS = ["www.example.com", "example.com"]
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")

Per la chiave segreta, genera una stringa robusta e salvala in un file protetto o in un secret manager. Se usi un file di ambiente, proteggilo con permessi stretti e documenta dove si trova. Esempio:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Per il database, la scelta più comune è PostgreSQL. Se vuoi partire in modo semplice e poi crescere, puoi usare SQLite solo per sviluppo o test. In produzione su CentOS 7, SQLite diventa presto il collo di bottiglia e introduce limiti inutili su concorrenza e backup.

Database: MySQL o PostgreSQL, ma con parametri chiari

Con Django, PostgreSQL è di solito la soluzione più lineare. Ti dà transazioni solide, tipi più ricchi e un comportamento prevedibile sotto carico. Se il tuo standard aziendale è MySQL/MariaDB, va bene lo stesso, purché installi i driver corretti e verifichi la compatibilità del charset.

Per PostgreSQL, installa il driver Python e configura il blocco database:

pip install psycopg2-binary
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "miosito",
        "USER": "miosito_user",
        "PASSWORD": os.environ.get("DJANGO_DB_PASSWORD"),
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

Dopo aver configurato il database, esegui le migrazioni e controlla che lo schema si crei senza errori:

python manage.py migrate

Se il comando fallisce, non passare al proxy per “vedere se funziona comunque”: il frontend web non corregge un database mal configurato.

Gunicorn come application server

Per pubblicare Django in modo corretto, Gunicorn è una scelta semplice e affidabile. Il server di sviluppo di Django non va usato fuori dal debug locale. Gunicorn gestisce meglio concorrenza e integrazione con systemd, e si presta bene a stare dietro un reverse proxy.

source /srv/django/miosito/venv/bin/activate
pip install gunicorn

Avvia un test manuale puntando al modulo WSGI del progetto:

gunicorn --bind 127.0.0.1:8001 config.wsgi:application

In un altro terminale verifica la risposta:

curl -I http://127.0.0.1:8001/

Se il test passa, puoi trasformare il comando in servizio systemd. Questo è il punto in cui conviene essere precisi con path assoluti, utente dedicato e directory di lavoro.

Servizio systemd per Django

Un’unità systemd ben fatta evita avvii manuali, rende il servizio riavviabile e permette di integrare log e dipendenze in modo pulito. Il file va messo in /etc/systemd/system/gunicorn-miosito.service o in un nome coerente con il tuo standard.

[Unit]
Description=Gunicorn for miosito
After=network.target

[Service]
User=djangoapp
Group=djangoapp
WorkingDirectory=/srv/django/miosito
Environment="DJANGO_SECRET_KEY=change_me"
Environment="DJANGO_DB_PASSWORD=change_me"
ExecStart=/srv/django/miosito/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8001 config.wsgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

Se usi variabili sensibili, non lasciarle in chiaro dentro l’unità. Meglio un file separato in /etc/sysconfig/ o /etc/default/ con permessi stretti, oppure un secret store. Dopo aver creato il servizio, ricarica systemd e avvialo:

sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn-miosito
sudo systemctl status gunicorn-miosito

Il check minimo è che il servizio sia active (running) e che journalctl -u gunicorn-miosito -n 50 non mostri trace di import o permessi.

Apache o Nginx davanti a Django

Su CentOS 7, Apache resta una scelta comune quando vuoi integrare virtual host, moduli maturi e configurazioni già presenti. Nginx è altrettanto valido e spesso più snello come reverse proxy. La logica non cambia: il web server ascolta sulla porta 80 o 443 e inoltra verso Gunicorn su localhost.

Con Apache, abilita un virtual host con proxy verso la porta interna:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8001/
    ProxyPassReverse / http://127.0.0.1:8001/
</VirtualHost>

Assicurati che i moduli proxy siano attivi e che il firewall consenta il traffico in ingresso sulle porte pubbliche:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Con Nginx la struttura è simile, ma il file di configurazione cambia sintassi. In entrambi i casi, il test da fare subito è una richiesta HTTP verso il dominio pubblico e una verifica diretta sulla porta interna. Se il proxy risponde ma l’app no, il problema è quasi sempre nel backend Gunicorn, nei permessi o in SELinux.

SELinux: il dettaglio che blocca più installazioni del previsto

Su CentOS 7 SELinux spesso è attivo e, se lo ignori, il setup sembra corretto ma il proxy non riesce a parlare con il backend. Non conviene disabilitarlo per comodità. Meglio capire cosa sta bloccando e applicare la policy minima necessaria.

Per un reverse proxy verso una porta locale, controlla innanzitutto i boolean di Apache se usi httpd:

getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect on

Se il servizio ascolta su una porta non standard e SELinux la blocca, aggiungi il contesto corretto. Prima verifica il tipo di porta consentito e poi applica la modifica in modo esplicito:

sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8001

Per capire se SELinux è il vero colpevole, osserva i log di audit:

sudo ausearch -m AVC -ts recent

Se trovi denial coerenti con il tuo servizio, non andare a tentativi: correggi il contesto o il boolean giusto e riprova. È un passaggio che vale la pena fare subito, perché riduce molto i “funziona in locale ma non dietro il proxy”.

Static files, media e raccolta asset

Django non dovrebbe servire i file statici direttamente in produzione. Usa collectstatic e fai pubblicare gli asset dal web server. Anche qui la separazione è utile: il backend applicativo non deve gestire ogni richiesta di immagine, CSS o JavaScript.

python manage.py collectstatic

Imposta in settings.py un percorso chiaro:

STATIC_URL = "/static/"
STATIC_ROOT = "/srv/django/miosito/staticfiles/"
MEDIA_URL = "/media/"
MEDIA_ROOT = "/srv/django/miosito/media/"

Nel virtual host configura il mapping delle directory statiche. Se manca questo pezzo, la pagina può aprirsi ma apparire incompleta o “rotta”, e spesso viene scambiato per un problema di Django quando in realtà è solo un errore di pubblicazione asset.

Hardening minimo e manutenzione

Una volta che l’app gira, il lavoro non è finito. Devi controllare aggiornamenti, permessi, log e backup. Il minimo sindacale è questo: aggiornare periodicamente il sistema, mantenere le dipendenze Python bloccate in modo ragionato, ruotare i log e testare il ripristino del database.

Controlla i permessi delle directory applicative:

namei -l /srv/django/miosito
ls -ld /srv/django/miosito /srv/django/miosito/venv

Monitora i log del servizio e del proxy. Su CentOS 7, i punti da guardare di solito sono journalctl, i log di Apache o Nginx e gli eventuali log applicativi scritti da Django. Se l’app va lenta, la prima metrica utile non è “sensazione”: sono error rate, tempo di risposta e saturazione del backend.

Per un controllo rapido della salute, tieni almeno questi test operativi:

systemctl is-active gunicorn-miosito
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://127.0.0.1:8001/
journalctl -u gunicorn-miosito -n 20 --no-pager

Se il sito è esposto su HTTPS, aggiungi il certificato e il rinnovo automatico prima di considerare il lavoro chiuso. Su installazioni vecchie è un classico dimenticarsene e ritrovarsi con downtime evitabile quando il certificato scade.

Sequenza pratica consigliata

Se vuoi una traccia operativa sintetica, la sequenza sensata è questa: prepara il sistema, installa Python 3, crea utente e directory, attiva il virtualenv, installa Django, genera il progetto, verifica le migrazioni, avvia Gunicorn, pubblica dietro Apache o Nginx, poi rifinisci SELinux, static files e HTTPS. Saltare uno di questi passaggi di solito significa tornare indietro più tardi con più rumore e meno contesto.

Su CentOS 7 il vero vantaggio non è “far partire Django”, ma farlo partire in modo ripetibile. Se il progetto è isolato, il servizio è gestito da systemd, il proxy è chiaro e i segreti non stanno nel repository, hai una base che si mantiene senza acrobazie. Assunzione: il reader abbia accesso sudo e un dominio già puntato verso il server; in caso contrario, chiudi prima DNS e permessi, poi riparti dalla configurazione web.