{"id":4995,"date":"2026-02-11T20:39:24","date_gmt":"2026-02-11T17:39:24","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/vps-hosting-for-django-and-flask-with-gunicorn-uvicorn-nginx-and-ssl\/"},"modified":"2026-02-11T20:39:24","modified_gmt":"2026-02-11T17:39:24","slug":"vps-hosting-for-django-and-flask-with-gunicorn-uvicorn-nginx-and-ssl","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/vps-hosting-for-django-and-flask-with-gunicorn-uvicorn-nginx-and-ssl\/","title":{"rendered":"VPS Hosting for Django and Flask with Gunicorn, Uvicorn, Nginx and SSL"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>If you are moving a Django or Flask project from local development to production, one of the most practical steps is putting it on a <a href=\"https:\/\/www.dchost.com\/vps\">VPS<\/a> with a proper web stack: an application server (Gunicorn or Uvicorn), Nginx as reverse proxy, and a solid SSL\/TLS setup. This combination is battle\u2011tested, efficient, and flexible enough for everything from small side projects to busy SaaS dashboards. On the dchost.com side, we see the same pattern over and over: teams start with the built\u2011in development server, then quickly realize they need something more robust, secure, and observable. In this guide, we will walk through the architecture, configuration patterns, and small but critical details that make Python apps stable in production. You will see where Gunicorn vs Uvicorn fits, how Nginx should be configured, and how to handle HTTPS and certificate automation without turning your weekends into maintenance windows.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_VPS_Hosting_Fits_Django_and_Flask_Projects\"><span class=\"toc_number toc_depth_1\">1<\/span> Why VPS Hosting Fits Django and Flask Projects<\/a><ul><li><a href=\"#Control_and_flexibility\"><span class=\"toc_number toc_depth_2\">1.1<\/span> Control and flexibility<\/a><\/li><li><a href=\"#Performance_and_predictable_resources\"><span class=\"toc_number toc_depth_2\">1.2<\/span> Performance and predictable resources<\/a><\/li><li><a href=\"#Security_and_compliance\"><span class=\"toc_number toc_depth_2\">1.3<\/span> Security and compliance<\/a><\/li><\/ul><\/li><li><a href=\"#Planning_Your_VPS_for_Django_and_Flask\"><span class=\"toc_number toc_depth_1\">2<\/span> Planning Your VPS for Django and Flask<\/a><ul><li><a href=\"#Choosing_OS_and_baseline_specs\"><span class=\"toc_number toc_depth_2\">2.1<\/span> Choosing OS and baseline specs<\/a><\/li><li><a href=\"#Basic_server_preparation\"><span class=\"toc_number toc_depth_2\">2.2<\/span> Basic server preparation<\/a><\/li><li><a href=\"#Python_environment_and_project_layout\"><span class=\"toc_number toc_depth_2\">2.3<\/span> Python environment and project layout<\/a><\/li><\/ul><\/li><li><a href=\"#Gunicorn_vs_Uvicorn_WSGI_vs_ASGI_in_Real_Life\"><span class=\"toc_number toc_depth_1\">3<\/span> Gunicorn vs Uvicorn: WSGI vs ASGI in Real Life<\/a><ul><li><a href=\"#Understanding_WSGI_and_ASGI\"><span class=\"toc_number toc_depth_2\">3.1<\/span> Understanding WSGI and ASGI<\/a><\/li><li><a href=\"#When_to_choose_Gunicorn\"><span class=\"toc_number toc_depth_2\">3.2<\/span> When to choose Gunicorn<\/a><\/li><li><a href=\"#When_to_choose_Uvicorn_or_Uvicorn_workers_under_Gunicorn\"><span class=\"toc_number toc_depth_2\">3.3<\/span> When to choose Uvicorn (or Uvicorn workers under Gunicorn)<\/a><\/li><li><a href=\"#Worker_count_and_timeouts\"><span class=\"toc_number toc_depth_2\">3.4<\/span> Worker count and timeouts<\/a><\/li><\/ul><\/li><li><a href=\"#Systemd_Services_Keeping_GunicornUvicorn_Running\"><span class=\"toc_number toc_depth_1\">4<\/span> Systemd Services: Keeping Gunicorn\/Uvicorn Running<\/a><ul><li><a href=\"#Example_systemd_unit_for_Gunicorn\"><span class=\"toc_number toc_depth_2\">4.1<\/span> Example systemd unit for Gunicorn<\/a><\/li><li><a href=\"#Example_systemd_unit_for_Uvicorn\"><span class=\"toc_number toc_depth_2\">4.2<\/span> Example systemd unit for Uvicorn<\/a><\/li><\/ul><\/li><li><a href=\"#Nginx_as_a_Reverse_Proxy_in_Front_of_DjangoFlask\"><span class=\"toc_number toc_depth_1\">5<\/span> Nginx as a Reverse Proxy in Front of Django\/Flask<\/a><ul><li><a href=\"#Installing_Nginx\"><span class=\"toc_number toc_depth_2\">5.1<\/span> Installing Nginx<\/a><\/li><li><a href=\"#Basic_Nginx_server_block\"><span class=\"toc_number toc_depth_2\">5.2<\/span> Basic Nginx server block<\/a><\/li><li><a href=\"#Static_and_media_files\"><span class=\"toc_number toc_depth_2\">5.3<\/span> Static and media files<\/a><\/li><\/ul><\/li><li><a href=\"#Enabling_HTTPS_SSLTLS_and_Letrsquos_Encrypt\"><span class=\"toc_number toc_depth_1\">6<\/span> Enabling HTTPS: SSL\/TLS and Let&rsquo;s Encrypt<\/a><ul><li><a href=\"#Letrsquos_Encrypt_and_Certbot\"><span class=\"toc_number toc_depth_2\">6.1<\/span> Let&rsquo;s Encrypt and Certbot<\/a><\/li><li><a href=\"#Manual_Nginx_TLS_configuration\"><span class=\"toc_number toc_depth_2\">6.2<\/span> Manual Nginx TLS configuration<\/a><\/li><li><a href=\"#Certificate_automation_and_renewals\"><span class=\"toc_number toc_depth_2\">6.3<\/span> Certificate automation and renewals<\/a><\/li><\/ul><\/li><li><a href=\"#Security_and_Observability_Basics_for_Python_Apps_on_a_VPS\"><span class=\"toc_number toc_depth_1\">7<\/span> Security and Observability Basics for Python Apps on a VPS<\/a><ul><li><a href=\"#Firewall_and_surface_reduction\"><span class=\"toc_number toc_depth_2\">7.1<\/span> Firewall and surface reduction<\/a><\/li><li><a href=\"#HTTP_security_headers\"><span class=\"toc_number toc_depth_2\">7.2<\/span> HTTP security headers<\/a><\/li><li><a href=\"#Logs_and_monitoring\"><span class=\"toc_number toc_depth_2\">7.3<\/span> Logs and monitoring<\/a><\/li><\/ul><\/li><li><a href=\"#End-to-End_Example_Deploying_Django_on_a_dchostcom_VPS\"><span class=\"toc_number toc_depth_1\">8<\/span> End-to-End Example: Deploying Django on a dchost.com VPS<\/a><ul><li><a href=\"#1_Prepare_the_server\"><span class=\"toc_number toc_depth_2\">8.1<\/span> 1. Prepare the server<\/a><\/li><li><a href=\"#2_Deploy_the_application\"><span class=\"toc_number toc_depth_2\">8.2<\/span> 2. Deploy the application<\/a><\/li><li><a href=\"#3_Configure_Gunicorn_WSGI\"><span class=\"toc_number toc_depth_2\">8.3<\/span> 3. Configure Gunicorn (WSGI)<\/a><\/li><li><a href=\"#4_Configure_Nginx\"><span class=\"toc_number toc_depth_2\">8.4<\/span> 4. Configure Nginx<\/a><\/li><li><a href=\"#5_Enable_HTTPS\"><span class=\"toc_number toc_depth_2\">8.5<\/span> 5. Enable HTTPS<\/a><\/li><li><a href=\"#6_Final_hardening_and_monitoring\"><span class=\"toc_number toc_depth_2\">8.6<\/span> 6. Final hardening and monitoring<\/a><\/li><\/ul><\/li><li><a href=\"#Summary_A_Solid_Production_Foundation_for_Django_and_Flask_on_dchostcom\"><span class=\"toc_number toc_depth_1\">9<\/span> Summary: A Solid Production Foundation for Django and Flask on dchost.com<\/a><\/li><\/ul><\/div>\n<h2><span id=\"Why_VPS_Hosting_Fits_Django_and_Flask_Projects\">Why VPS Hosting Fits Django and Flask Projects<\/span><\/h2>\n<p>Before touching configuration files, it helps to be clear why running Django and Flask on a VPS is such a common choice.<\/p>\n<h3><span id=\"Control_and_flexibility\">Control and flexibility<\/span><\/h3>\n<p>With a VPS you control:<\/p>\n<ul>\n<li><strong>Linux distribution and versions<\/strong> \u2013 Choose Ubuntu, Debian, AlmaLinux, etc. with the Python versions you actually need.<\/li>\n<li><strong>System packages<\/strong> \u2013 Install system libraries (e.g. Postgres client libs, image processing tools) without fighting shared hosting limitations.<\/li>\n<li><strong>Network and firewall<\/strong> \u2013 Open only the ports you want, restrict SSH, and harden the box to your security standards.<\/li>\n<li><strong>Background services<\/strong> \u2013 Run Celery, Redis, cron jobs, WebSocket servers, and other components next to your app.<\/li>\n<\/ul>\n<h3><span id=\"Performance_and_predictable_resources\">Performance and predictable resources<\/span><\/h3>\n<p>Django and Flask are often backed by databases, queues, and caching layers. They benefit from:<\/p>\n<ul>\n<li><strong>Dedicated vCPU and RAM<\/strong> \u2013 Your workers are not fighting with unrelated websites running heavy plugins.<\/li>\n<li><strong>NVMe or SSD storage<\/strong> \u2013 Faster disk I\/O is critical for database and log performance.<\/li>\n<li><strong>Stable process management<\/strong> \u2013 Gunicorn\/Uvicorn worker counts, memory usage, and timeouts tuned exactly for your workload.<\/li>\n<\/ul>\n<p>We covered how to read resource signals (CPU steal, IO wait, RAM pressure) in detail for other stacks; the same thinking applies to Python apps. If you want a deeper dive into VPS resource behaviour, our article on <a href=\"https:\/\/www.dchost.com\/blog\/en\/vps-kaynak-kullanimi-izleme-rehberi-htop-iotop-netdata-ve-prometheus\/\">monitoring VPS resource usage with htop, iotop, Netdata and Prometheus<\/a> is a good companion read.<\/p>\n<h3><span id=\"Security_and_compliance\">Security and compliance<\/span><\/h3>\n<p>Many Django\/Flask apps handle personal data, internal dashboards or business\u2011critical workflows. A VPS makes it easier to:<\/p>\n<ul>\n<li>Lock down SSH and admin ports.<\/li>\n<li>Apply OS\u2011level hardening (kernel params, firewall, Fail2ban).<\/li>\n<li>Implement proper logging and retention for audits.<\/li>\n<\/ul>\n<p>For a step\u2011by\u2011step checklist, see our <a href=\"https:\/\/www.dchost.com\/blog\/en\/vps-guvenlik-sertlestirme-kontrol-listesi-sshd_config-fail2ban-ve-root-erisimini-kapatmak\/\">VPS security hardening checklist with sshd_config and Fail2ban<\/a>.<\/p>\n<h2><span id=\"Planning_Your_VPS_for_Django_and_Flask\">Planning Your VPS for Django and Flask<\/span><\/h2>\n<h3><span id=\"Choosing_OS_and_baseline_specs\">Choosing OS and baseline specs<\/span><\/h3>\n<p>Most Python teams today choose a recent LTS Linux distribution, such as Ubuntu LTS or Debian stable. Look for:<\/p>\n<ul>\n<li><strong>Long support window<\/strong> (5+ years) so you are not forced into frequent OS migrations.<\/li>\n<li><strong>Modern OpenSSL and TLS libraries<\/strong> for up\u2011to\u2011date HTTPS and HTTP\/2\/3 support.<\/li>\n<\/ul>\n<p>As a rough starting point for a single small\u2011to\u2011medium Django\/Flask app:<\/p>\n<ul>\n<li><strong>1\u20132 vCPUs<\/strong> \u2013 Enough for a few Gunicorn\/Uvicorn workers and occasional background jobs.<\/li>\n<li><strong>2\u20134 GB RAM<\/strong> \u2013 Gives space for the app, OS, database client, and caching.<\/li>\n<li><strong>20\u201340 GB NVMe\/SSD<\/strong> \u2013 App code, logs, virtualenvs, and basic database usage.<\/li>\n<\/ul>\n<p>Larger SaaS apps or API backends with significant traffic will often start at 4+ vCPUs and 8+ GB RAM, sometimes with separate database and Redis servers. Our article on <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-blog-woocommerce-ve-saas-icin-kac-cpu-ne-kadar-ram\/\">how many vCPUs and how much RAM you really need<\/a> shows how we think about sizing for web apps; the logic translates well to Python frameworks.<\/p>\n<h3><span id=\"Basic_server_preparation\">Basic server preparation<\/span><\/h3>\n<p>Once your dchost.com VPS is provisioned and you can log in via SSH, apply a simple baseline:<\/p>\n<ol>\n<li>Update packages:\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt update &amp;&amp; sudo apt upgrade<\/code><\/pre>\n<\/li>\n<li>Create a non\u2011root user and grant sudo.<\/li>\n<li>Configure SSH keys, disable password login, optionally move SSH to a non\u2011standard port.<\/li>\n<li>Enable a firewall (e.g. ufw) allowing only SSH, HTTP (80) and HTTPS (443).<\/li>\n<\/ol>\n<p>We walk through these first\u2011day steps in more detail in <a href=\"https:\/\/www.dchost.com\/blog\/en\/yeni-vpste-ilk-24-saat-guncelleme-guvenlik-duvari-ve-kullanici-hesaplari\/\">our guide to the first 24 hours on a new VPS<\/a>.<\/p>\n<h3><span id=\"Python_environment_and_project_layout\">Python environment and project layout<\/span><\/h3>\n<p>For Django and Flask deployments, we strongly recommend:<\/p>\n<ul>\n<li>Using <code>python3 -m venv<\/code> for a project\u2011local virtual environment.<\/li>\n<li>Keeping application code under <code>\/srv\/&lt;project&gt;<\/code> or <code>\/var\/www\/&lt;project&gt;<\/code> with clear ownership (dedicated Unix user).<\/li>\n<li>Separating configuration via environment variables or a <code>.env<\/code> file, never hard\u2011coding secrets in the repo.<\/li>\n<\/ul>\n<p>If you have not yet formalised your secrets handling, check our piece on <a href=\"https:\/\/www.dchost.com\/blog\/en\/vpste-env-ve-gizli-anahtar-yonetimi\/\">managing .env files and secrets on a VPS safely<\/a> for patterns that work beyond the first deployment.<\/p>\n<h2><span id=\"Gunicorn_vs_Uvicorn_WSGI_vs_ASGI_in_Real_Life\">Gunicorn vs Uvicorn: WSGI vs ASGI in Real Life<\/span><\/h2>\n<h3><span id=\"Understanding_WSGI_and_ASGI\">Understanding WSGI and ASGI<\/span><\/h3>\n<p>Historically, Python web frameworks used <strong>WSGI<\/strong> (Web Server Gateway Interface) \u2013 a synchronous interface. Gunicorn is a popular WSGI application server, perfect for classic Django and Flask apps.<\/p>\n<p>Modern frameworks introduce <strong>ASGI<\/strong> (Asynchronous Server Gateway Interface), which supports async views, WebSockets, and long\u2011lived connections. Uvicorn and Daphne are common ASGI servers. Django now supports ASGI; Flask has community ASGI wrappers and many ecosystems (e.g. FastAPI) are ASGI\u2011first.<\/p>\n<h3><span id=\"When_to_choose_Gunicorn\">When to choose Gunicorn<\/span><\/h3>\n<p>Gunicorn is an excellent default when:<\/p>\n<ul>\n<li>You run a primarily synchronous Django or Flask app.<\/li>\n<li>You do not heavily rely on WebSockets or background streaming in the same process.<\/li>\n<li>You want a stable, well\u2011tested server with simple process management.<\/li>\n<\/ul>\n<p>Basic Gunicorn command for a Django project:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cd \/srv\/myproject\nsource venv\/bin\/activate\ngunicorn myproject.wsgi:application \n  --bind 127.0.0.1:8000 \n  --workers 3 \n  --timeout 30<\/code><\/pre>\n<h3><span id=\"When_to_choose_Uvicorn_or_Uvicorn_workers_under_Gunicorn\">When to choose Uvicorn (or Uvicorn workers under Gunicorn)<\/span><\/h3>\n<p>Uvicorn shines when:<\/p>\n<ul>\n<li>You use Django&rsquo;s async views, Channels, or an ASGI\u2011first framework.<\/li>\n<li>You need WebSockets for live dashboards, chats, or notifications.<\/li>\n<li>You expect many long\u2011lived connections with moderate CPU usage.<\/li>\n<\/ul>\n<p>Uvicorn can be run directly:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">uvicorn myproject.asgi:application \n  --host 127.0.0.1 --port 8000 \n  --workers 3<\/code><\/pre>\n<p>Or you can use Gunicorn as a process manager with Uvicorn workers:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">gunicorn myproject.asgi:application \n  -k uvicorn.workers.UvicornWorker \n  --bind 127.0.0.1:8000 \n  --workers 3<\/code><\/pre>\n<p>This hybrid approach gives you Gunicorn&rsquo;s familiar config style with ASGI performance.<\/p>\n<h3><span id=\"Worker_count_and_timeouts\">Worker count and timeouts<\/span><\/h3>\n<p>A simple rule of thumb:<\/p>\n<ul>\n<li><strong>CPU\u2011bound<\/strong> workloads: ~<code>2 x vCPUs<\/code> workers.<\/li>\n<li><strong>IO\u2011bound<\/strong>\/light workloads: sometimes more workers, but monitor RAM and response times.<\/li>\n<\/ul>\n<p>For many Django\/Flask APIs on a 2\u2011vCPU VPS, starting with 3\u20134 workers is reasonable. Then watch CPU, load average, and request latency under traffic and adjust.<\/p>\n<h2><span id=\"Systemd_Services_Keeping_GunicornUvicorn_Running\">Systemd Services: Keeping Gunicorn\/Uvicorn Running<\/span><\/h2>\n<p>Manually running your application server from the shell is fine for tests, but in production we want a supervised service that starts on boot and restarts on failure. On most modern distributions, this means <strong>systemd<\/strong>.<\/p>\n<h3><span id=\"Example_systemd_unit_for_Gunicorn\">Example systemd unit for Gunicorn<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">[Unit]\nDescription=Gunicorn for myproject\nAfter=network.target\n\n[Service]\nUser=myproject\nGroup=myproject\nWorkingDirectory=\/srv\/myproject\nEnvironment=&quot;PATH=\/srv\/myproject\/venv\/bin&quot;\nExecStart=\/srv\/myproject\/venv\/bin\/gunicorn myproject.wsgi:application \n  --bind 127.0.0.1:8000 \n  --workers 3 \n  --timeout 30\n\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<p>Save this as <code>\/etc\/systemd\/system\/myproject-gunicorn.service<\/code>, then enable and start:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo systemctl daemon-reload\nsudo systemctl enable myproject-gunicorn\nsudo systemctl start myproject-gunicorn\nsudo systemctl status myproject-gunicorn<\/code><\/pre>\n<h3><span id=\"Example_systemd_unit_for_Uvicorn\">Example systemd unit for Uvicorn<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">[Unit]\nDescription=Uvicorn for myproject (ASGI)\nAfter=network.target\n\n[Service]\nUser=myproject\nGroup=myproject\nWorkingDirectory=\/srv\/myproject\nEnvironment=&quot;PATH=\/srv\/myproject\/venv\/bin&quot;\nExecStart=\/srv\/myproject\/venv\/bin\/uvicorn myproject.asgi:application \n  --host 127.0.0.1 --port 8000 --workers 3\n\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<p>Using systemd means your app comes back after reboots and crashes, and you get structured logs via <code>journalctl<\/code>.<\/p>\n<h2><span id=\"Nginx_as_a_Reverse_Proxy_in_Front_of_DjangoFlask\">Nginx as a Reverse Proxy in Front of Django\/Flask<\/span><\/h2>\n<p>Gunicorn\/Uvicorn are great at executing Python code, but not ideal for handling TLS, HTTP\/2, slow clients, and static file caching. That is where <strong>Nginx<\/strong> sits in front as a reverse proxy.<\/p>\n<h3><span id=\"Installing_Nginx\">Installing Nginx<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt install nginx<\/code><\/pre>\n<p>Once installed, Nginx will likely be listening on port 80. We will create a <a href=\"https:\/\/www.dchost.com\/dedicated-server\">dedicated server<\/a> block for your domain, proxying to Gunicorn\/Uvicorn on <code>127.0.0.1:8000<\/code>.<\/p>\n<h3><span id=\"Basic_Nginx_server_block\">Basic Nginx server block<\/span><\/h3>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    # Increase buffer sizes slightly if you have large headers\/cookies\n    client_max_body_size 20m;\n\n    location \/static\/ {\n        alias \/srv\/myproject\/static\/;\n    }\n\n    location \/media\/ {\n        alias \/srv\/myproject\/media\/;\n    }\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:8000;\n\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        proxy_read_timeout 60s;\n        proxy_connect_timeout 5s;\n        proxy_redirect off;\n    }\n}<\/code><\/pre>\n<p>Enable the config and reload Nginx:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo ln -s \/etc\/nginx\/sites-available\/myproject.conf \/etc\/nginx\/sites-enabled\/\nsudo nginx -t\nsudo systemctl reload nginx<\/code><\/pre>\n<p>If you want a broader view of reverse proxy patterns (including load balancing multiple backends), our practical guide on <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-reverse-proxy-ve-basit-load-balancer-kurulumu-kucuk-projeler-icin-uygulamali-rehber\/\">Nginx reverse proxy and simple load balancer setup for small projects<\/a> is worth exploring.<\/p>\n<h3><span id=\"Static_and_media_files\">Static and media files<\/span><\/h3>\n<p>In Django, you typically run <code>python manage.py collectstatic<\/code> to collect static assets into a directory like <code>\/srv\/myproject\/static\/<\/code>, which Nginx serves directly via <code>alias<\/code>. Flask projects often either use a similar pattern or let Nginx serve static files from a <code>static<\/code> folder within the project.<\/p>\n<p>Serving static and media from Nginx offloads this lightweight work from your Python workers and simplifies caching and compression rules.<\/p>\n<h2><span id=\"Enabling_HTTPS_SSLTLS_and_Letrsquos_Encrypt\">Enabling HTTPS: SSL\/TLS and Let&rsquo;s Encrypt<\/span><\/h2>\n<p>Running Django or Flask in production without HTTPS is no longer acceptable: browsers show warnings, APIs refuse insecure callbacks, and security baselines fail. The good news is that free certificates and automation have made proper TLS straightforward.<\/p>\n<h3><span id=\"Letrsquos_Encrypt_and_Certbot\">Let&rsquo;s Encrypt and Certbot<\/span><\/h3>\n<p>The most common approach on a VPS is Let&rsquo;s Encrypt via Certbot:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt install certbot python3-certbot-nginx\nsudo certbot --nginx -d example.com -d www.example.com<\/code><\/pre>\n<p>Certbot will:<\/p>\n<ul>\n<li>Verify domain ownership using an HTTP\u201101 challenge.<\/li>\n<li>Obtain a certificate and configure Nginx to use it.<\/li>\n<li>Optionally set up automatic HTTP\u2192HTTPS redirects.<\/li>\n<\/ul>\n<p>By default, it also installs a cron\/systemd timer to renew the certificate automatically.<\/p>\n<h3><span id=\"Manual_Nginx_TLS_configuration\">Manual Nginx TLS configuration<\/span><\/h3>\n<p>If you prefer manual control, your HTTPS server block will look like:<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate     \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;\n\n    ssl_protocols TLSv1.2 TLSv1.3;\n    ssl_prefer_server_ciphers on;\n\n    # Optionally add HSTS (after testing!)\n    # add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always;\n\n    client_max_body_size 20m;\n\n    location \/static\/ {\n        alias \/srv\/myproject\/static\/;\n    }\n\n    location \/media\/ {\n        alias \/srv\/myproject\/media\/;\n    }\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}<\/code><\/pre>\n<p>You can then keep a lightweight HTTP server block that only redirects to HTTPS:<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">server {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\/\/$host$request_uri;\n}<\/code><\/pre>\n<p>We explain the SEO and security side of full HTTPS migrations (including HSTS and canonical URLs) in our <a href=\"https:\/\/www.dchost.com\/blog\/en\/httpden-httpsye-gecis-rehberi-seo-kayipsiz-ssl-migrasyonu-hsts-ve-canonical-ayarlari\/\">full HTTP to HTTPS migration guide with HSTS and canonical settings<\/a>.<\/p>\n<h3><span id=\"Certificate_automation_and_renewals\">Certificate automation and renewals<\/span><\/h3>\n<p>Certificates have short lifetimes (often 90 days), so automation matters. Certbot timers usually handle renewals automatically, but you should still:<\/p>\n<ul>\n<li>Test renewal with <code>sudo certbot renew --dry-run<\/code>.<\/li>\n<li>Monitor certificate expiry with external checks or a simple cron script.<\/li>\n<li>Reload Nginx after successful renewals (Certbot typically does this for you).<\/li>\n<\/ul>\n<p>For more complex setups (wildcards, DNS\u201101 challenges, multi\u2011tenant SaaS), we recommend our piece on <a href=\"https:\/\/www.dchost.com\/blog\/en\/ssl-sertifika-otomasyon-araclari-acme-panel-entegrasyonlari-ve-dns-01-stratejileri\/\">SSL certificate automation tools and ACME strategies<\/a>, which shows how to scale auto\u2011SSL beyond a single site.<\/p>\n<h2><span id=\"Security_and_Observability_Basics_for_Python_Apps_on_a_VPS\">Security and Observability Basics for Python Apps on a VPS<\/span><\/h2>\n<h3><span id=\"Firewall_and_surface_reduction\">Firewall and surface reduction<\/span><\/h3>\n<p>Even a small Django or Flask app benefits from basic network hardening:<\/p>\n<ul>\n<li>Allow only SSH, HTTP, and HTTPS on the public interface.<\/li>\n<li>Bind Gunicorn\/Uvicorn to <code>127.0.0.1<\/code> only, never to a public IP.<\/li>\n<li>Use Fail2ban to block repeated failed SSH and, optionally, admin login attempts.<\/li>\n<\/ul>\n<h3><span id=\"HTTP_security_headers\">HTTP security headers<\/span><\/h3>\n<p>Django and Flask both support setting HTTP security headers from the app layer or via Nginx. Typical ones include:<\/p>\n<ul>\n<li><code>Strict-Transport-Security<\/code> (HSTS)<\/li>\n<li><code>Content-Security-Policy<\/code> (CSP)<\/li>\n<li><code>X-Frame-Options<\/code><\/li>\n<li><code>X-Content-Type-Options<\/code><\/li>\n<\/ul>\n<p>You can configure many of them directly in Nginx with <code>add_header<\/code> directives. If you want a structured walk\u2011through with examples for different setups, see our <a href=\"https:\/\/www.dchost.com\/blog\/en\/http-guvenlik-basliklari-rehberi-shared-hosting-ve-vpste-csp-hsts-x-frame-options-ve-digerleri-nasil-ayarlanir\/\">HTTP security headers guide for shared hosting and VPS<\/a>.<\/p>\n<h3><span id=\"Logs_and_monitoring\">Logs and monitoring<\/span><\/h3>\n<p>At minimum, keep an eye on:<\/p>\n<ul>\n<li><strong>Nginx access and error logs<\/strong> \u2013 Status codes, response times, spikes in 4xx\/5xx.<\/li>\n<li><strong>Gunicorn\/Uvicorn logs<\/strong> \u2013 Worker timeouts, crashes, import errors.<\/li>\n<li><strong>System metrics<\/strong> \u2013 CPU, RAM, disk space, I\/O, and network.<\/li>\n<\/ul>\n<p>For teams starting to professionalise their operations, our tutorial on <a href=\"https:\/\/www.dchost.com\/blog\/en\/vps-izleme-ve-alarm-kurulumu-prometheus-grafana-ve-uptime-kuma-ile-baslangic\/\">VPS monitoring and alerts with Prometheus, Grafana and Uptime Kuma<\/a> shows how to build a practical, alert\u2011driven observability stack without over\u2011engineering.<\/p>\n<h2><span id=\"End-to-End_Example_Deploying_Django_on_a_dchostcom_VPS\">End-to-End Example: Deploying Django on a dchost.com VPS<\/span><\/h2>\n<p>Let&rsquo;s combine everything into a concrete high\u2011level runbook for a typical Django app.<\/p>\n<h3><span id=\"1_Prepare_the_server\">1. Prepare the server<\/span><\/h3>\n<ul>\n<li>Provision a VPS from dchost.com in the region closest to your main users.<\/li>\n<li>Harden SSH, create a non\u2011root user, and enable a firewall (SSH, HTTP, HTTPS only).<\/li>\n<li>Install base packages: <code>python3-venv<\/code>, <code>python3-pip<\/code>, <code>git<\/code>, <code>nginx<\/code>, database client tools, and any OS libraries your project needs.<\/li>\n<\/ul>\n<h3><span id=\"2_Deploy_the_application\">2. Deploy the application<\/span><\/h3>\n<ul>\n<li>Create <code>\/srv\/myproject<\/code> and a dedicated <code>myproject<\/code> Unix user.<\/li>\n<li>Clone your repository into <code>\/srv\/myproject<\/code>.<\/li>\n<li>Create a virtualenv: <code>python3 -m venv \/srv\/myproject\/venv<\/code>.<\/li>\n<li>Install dependencies: <code>pip install -r requirements.txt<\/code>.<\/li>\n<li>Configure environment variables for <code>DJANGO_SETTINGS_MODULE<\/code>, database URL, secret key, etc.<\/li>\n<li>Run migrations (<code>manage.py migrate<\/code>) and collectstatic.<\/li>\n<\/ul>\n<h3><span id=\"3_Configure_Gunicorn_WSGI\">3. Configure Gunicorn (WSGI)<\/span><\/h3>\n<ul>\n<li>Test Gunicorn manually binding to 127.0.0.1:8000.<\/li>\n<li>Add a systemd service unit for <code>myproject-gunicorn<\/code> as shown above.<\/li>\n<li>Enable and start the service, confirm it stays up across reboots.<\/li>\n<\/ul>\n<h3><span id=\"4_Configure_Nginx\">4. Configure Nginx<\/span><\/h3>\n<ul>\n<li>Create an Nginx server block for your domain, with <code>proxy_pass<\/code> pointing to <code>http:\/\/127.0.0.1:8000<\/code>.<\/li>\n<li>Map <code>\/static\/<\/code> and <code>\/media\/<\/code> to the appropriate directories using <code>alias<\/code>.<\/li>\n<li>Enable the site, test Nginx config, and reload.<\/li>\n<\/ul>\n<h3><span id=\"5_Enable_HTTPS\">5. Enable HTTPS<\/span><\/h3>\n<ul>\n<li>Point your domain&rsquo;s A\/AAAA records to the VPS IP from your domain registrar or dchost.com domain panel.<\/li>\n<li>Run Certbot with the Nginx plugin to obtain and install a Let&rsquo;s Encrypt certificate.<\/li>\n<li>Verify that HTTP redirects to HTTPS and that the certificate chain is valid in browsers.<\/li>\n<\/ul>\n<h3><span id=\"6_Final_hardening_and_monitoring\">6. Final hardening and monitoring<\/span><\/h3>\n<ul>\n<li>Set up Fail2ban and tighten firewall rules if needed.<\/li>\n<li>Add basic HTTP security headers via Nginx or Django middleware.<\/li>\n<li>Configure resource and uptime monitoring (e.g. Prometheus\/Node Exporter and Uptime Kuma).<\/li>\n<li>Test backup and restore processes for your database and uploaded files.<\/li>\n<\/ul>\n<p>This workflow is almost identical for Flask, except for framework\u2011specific details (e.g. Flask&rsquo;s application object, configuration patterns). For async\u2011heavy workloads or WebSockets, you swap Gunicorn&rsquo;s WSGI entrypoint for Uvicorn\/ASGI while keeping Nginx and TLS architecture the same.<\/p>\n<h2><span id=\"Summary_A_Solid_Production_Foundation_for_Django_and_Flask_on_dchostcom\">Summary: A Solid Production Foundation for Django and Flask on dchost.com<\/span><\/h2>\n<p>Deploying Django and Flask on a VPS does not need to be dramatic. Once you understand the roles of each component, the stack is surprisingly simple: Nginx terminates TLS and handles clients; Gunicorn or Uvicorn runs your application; systemd keeps everything alive; Let&rsquo;s Encrypt (or other ACME automation) keeps certificates fresh. From the dchost.com side, this is the pattern we see most often in successful Python projects\u2014whether they start as internal tools, customer portals, or full\u2011blown SaaS products.<\/p>\n<p>If you are planning your next deployment, start by picking a dchost.com VPS size that matches your expected traffic, then follow the sequence above: secure the server, set up Python and your app server, wire Nginx in front, and finish with HTTPS, security headers, backups, and monitoring. As your traffic grows, you can layer on load balancing, separate database servers, or containers, but the fundamentals you built here will stay the same. With a clean Gunicorn\/Uvicorn + Nginx + SSL foundation, your Django and Flask apps have room to grow without forcing you into a complete re\u2011architecture on day one.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>If you are moving a Django or Flask project from local development to production, one of the most practical steps is putting it on a VPS with a proper web stack: an application server (Gunicorn or Uvicorn), Nginx as reverse proxy, and a solid SSL\/TLS setup. This combination is battle\u2011tested, efficient, and flexible enough for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4996,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-4995","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/4995","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=4995"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/4995\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/4996"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=4995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=4995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=4995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}