In config/settings/production.py:
DEBUG = False # Never True in production
SECURE_SSL_REDIRECT = True # Force HTTPS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True # Session cookie HTTPS only
CSRF_COOKIE_SECURE = True # CSRF cookie HTTPS only
SECURE_BROWSER_XSS_FILTER = True # XSS protection header
SECURE_CONTENT_TYPE_NOSNIFF = True # Prevent MIME sniffing
X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking
SECURE_HSTS_SECONDS = 31536000 # HSTS for 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# .env must be readable only by owner
chmod 600 /var/www/yealinbilling/.env
# App directory
sudo chown -R crip:www-data /var/www/yealinbilling
sudo chmod -R 755 /var/www/yealinbilling
sudo chmod -R 775 /var/www/yealinbilling/media
sudo chmod -R 775 /var/www/yealinbilling/logs
The socket must be accessible by www-data (Nginx) but not world-readable:
# Check socket permissions
ls -la /var/www/yealinbilling/gunicorn.sock
# Should show: srwxrwx--- crip www-data
# If wrong, fix:
sudo usermod -aG crip www-data
sudo systemctl restart gunicorn
cd /var/www/yealinbilling
source venv/bin/activate
export DJANGO_SETTINGS_MODULE=config.settings.production
python manage.py changepassword admin
from apps.portal.models import CustomerPortalUser
user = CustomerPortalUser.objects.get(email='customer@email.com')
user.set_password('new_password')
user.save()
# Change in PostgreSQL
sudo -u postgres psql
ALTER USER yealinbilling WITH PASSWORD 'new_password';
\q
# Then update .env
nano /var/www/yealinbilling/.env
# Update DB_PASSWORD=new_password
# Restart services
sudo systemctl restart gunicorn celery celerybeat
POST /api/auth/token/POST /api/portal/auth/token/ (contains is_portal=True)JWT tokens expire after 5 minutes (access) — refresh tokens extend sessions.
All secrets are stored in:
.env file on each server (chmod 600)Never:
.env to git (it's in .gitignore)