<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mostafa-DE]]></title><description><![CDATA[Software Engineer]]></description><link>https://blog.mostafade.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 01:47:27 GMT</lastBuildDate><atom:link href="https://blog.mostafade.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Security Defaults That Save You Before You Need Them]]></title><description><![CDATA[Most security conversations start with “how do we protect this?” as if protection is something you add later.But security isn’t an afterthought. It’s the sum of the defaults you began with.
Every new system, no matter how small, starts with assumptio...]]></description><link>https://blog.mostafade.com/security-defaults-that-save-you-before-you-need-them</link><guid isPermaLink="true">https://blog.mostafade.com/security-defaults-that-save-you-before-you-need-them</guid><category><![CDATA[Devops]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Sat, 11 Oct 2025 16:53:34 GMT</pubDate><content:encoded><![CDATA[<p>Most security conversations start with <em>“how do we protect this?”</em> as if protection is something you add later.<br />But security isn’t an afterthought. It’s the sum of the defaults you began with.</p>
<p>Every new system, <strong>no matter how small,</strong> starts with assumptions: which ports stay open, who gets access, what gets logged, and what alarms exist. Those defaults decide whether you’ll sleep well when something breaks.</p>
<blockquote>
<p>If you start new infra tomorrow, don’t think “how do I secure this?” think “what defaults would save me next time?”</p>
</blockquote>
<h2 id="heading-monitoring-comes-first">Monitoring Comes First</h2>
<p><strong>Monitor everything.</strong> Big things, small things, boring things. If it can change state or affect users, it’s worth observing.</p>
<ul>
<li><p><strong>Metrics</strong> for health and capacity (rates, errors, latency, saturation).</p>
</li>
<li><p><strong>Logs</strong> for actions and decisions (who, what, when, where).</p>
</li>
<li><p><strong>Traces</strong> for flow across boundaries (services, queues, data stores).</p>
</li>
<li><p><strong>Alarms</strong> for deviations (not noise).</p>
</li>
</ul>
<p>You’ll thank yourself later. Detection beats speculation.</p>
<blockquote>
<p>A security checklist is only as strong as the next engineer who forgets to read it.</p>
</blockquote>
<p>That’s why monitoring and alerts must be defaults, created automatically with every new component, not a to-do you remember later.</p>
<h2 id="heading-trust-nobody-including-yourself">Trust Nobody (Including Yourself)</h2>
<p>Work cultures thrive on trust and ownership, but security has a different rule:</p>
<ul>
<li><p><strong>Assume compromise is possible.</strong></p>
</li>
<li><p><strong>Assume you’ll make mistakes.</strong></p>
</li>
<li><p><strong>Design so mistakes are contained.</strong></p>
</li>
</ul>
<p>Concretely:</p>
<ul>
<li><p><strong>Least privilege by default.</strong> Roles grant the minimum required, nothing more.</p>
</li>
<li><p><strong>Access expires by default.</strong> Temporary elevation beats permanent power.</p>
</li>
<li><p><strong>Separation of duties.</strong> No single actor can deploy, approve, and access sensitive data.</p>
</li>
<li><p><strong>Logs and alarms everywhere.</strong> Trust is verified by evidence, not optimism.</p>
</li>
</ul>
<h2 id="heading-log-actions-all-of-them">Log Actions. All of Them.</h2>
<p>Logs are your closest friends. Keep them around.</p>
<ul>
<li><p><strong>Log every action</strong> that changes state or touches sensitive paths.</p>
</li>
<li><p><strong>Make logs structured</strong> (machine-readable keys, not prose).</p>
</li>
<li><p><strong>Centralise and retain</strong> with sensible lifetimes for forensics.</p>
</li>
<li><p><strong>Alert on behaviour, not just errors</strong> (for example, unusual access patterns).</p>
</li>
</ul>
<blockquote>
<p>Log everything that’s an action. Not secrets, not personal data, evidence of decisions. That’s your paper trail when you need it most.</p>
</blockquote>
<h2 id="heading-automate-principles-not-tools">Automate Principles, Not Tools</h2>
<p>Security that depends on memory is security that won’t scale.</p>
<ul>
<li><p><strong>Infrastructure-as-Code modules or templates</strong> should bake in secure defaults (deny-by-default networking, baseline logging, required alarms).</p>
</li>
<li><p><strong>Service or application scaffolds</strong> should enable safe configs out of the box (sane auth flows, rate limits, health checks).</p>
</li>
<li><p><strong>Pipelines or policies</strong> should enforce reviews for risky changes (identity, network, data).</p>
</li>
</ul>
<p>The message: make the right thing the easy thing.</p>
<h2 id="heading-beware-security-theater">Beware Security Theater</h2>
<p>More rules can mean more latency, more cost, more complexity, and sometimes less clarity.</p>
<ul>
<li><p>If twenty fragile rules slow the system, ask: <em>Can I remove the attack surface instead?</em></p>
</li>
<li><p>If a mitigation “works” but hides root causes, refactor the design.</p>
</li>
<li><p>Prefer <strong>simpler architecture</strong> and <strong>clear boundaries</strong> over complex patchwork.</p>
</li>
</ul>
<p>Be mindful, keep learning, and be willing to replace yesterday’s “must-have” with a cleaner idea tomorrow.</p>
<h2 id="heading-security-is-a-mindset-on-and-off-the-clock">Security Is a Mindset (On and Off the Clock)</h2>
<p>Security is a habit loop:</p>
<ul>
<li><p><strong>Default to scepticism</strong> of new permissions and open surfaces.</p>
</li>
<li><p><strong>Default to visibility</strong> (metrics, logs, traces).</p>
</li>
<li><p><strong>Default to containment</strong> (blast-radius thinking).</p>
</li>
<li><p><strong>Default to reflection</strong> (post-incident learning, not blame).</p>
</li>
</ul>
<p>It’s not just at work. The same instincts, least privilege, scepticism, and good hygiene also make sense in daily life.</p>
<h2 id="heading-a-tool-agnostic-default-baseline">A Tool-Agnostic Default Baseline</h2>
<h3 id="heading-identity-amp-access">Identity &amp; Access</h3>
<ul>
<li><p>Roles are least-privilege and scoped.</p>
</li>
<li><p>Strong authentication and multiple factors; break-glass account exists and is monitored.</p>
</li>
<li><p>Access grants are time-boxed and reviewed.</p>
</li>
</ul>
<h3 id="heading-network">Network</h3>
<ul>
<li><p>Deny by default; open only what’s required.</p>
</li>
<li><p>Segment internal vs. external paths; control egress.</p>
</li>
<li><p>Encrypt in transit by default.</p>
</li>
</ul>
<h3 id="heading-data">Data</h3>
<ul>
<li><p>Encrypt at rest by default; rotate keys on a schedule.</p>
</li>
<li><p>Backups exist, are isolated, and restores are tested.</p>
</li>
<li><p>Access to sensitive data is logged and alerted.</p>
</li>
</ul>
<h3 id="heading-runtime-app">Runtime / App</h3>
<ul>
<li><p>Minimal surface area; healthy rate limits; sane timeouts.</p>
</li>
<li><p>Health checks and circuit breakers to avoid cascading failures.</p>
</li>
<li><p>Safe defaults for configuration; secrets never in code or logs.</p>
</li>
</ul>
<h3 id="heading-observability">Observability</h3>
<ul>
<li><p>Metrics, logs, traces on day one.</p>
</li>
<li><p>Alarms tuned to behaviour, with clear owners and runbooks.</p>
</li>
<li><p>Centralized storage and practical retention.</p>
</li>
</ul>
<h3 id="heading-process-amp-culture">Process &amp; Culture</h3>
<ul>
<li><p>Reviews required for identity, network, and data changes.</p>
</li>
<li><p>Incident channel, calm communication, and blameless postmortems.</p>
</li>
<li><p>Regular drills for recovery and access revocation.  </p>
</li>
</ul>
<p>Security isn’t a set of tools. It’s a set of defaults that reflect how seriously you take mistakes before they happen. The stronger your defaults, the less you need to think about security day to day.</p>
<blockquote>
<p>If you start new infra tomorrow, don’t think “how do I secure this?” think “what defaults would save me next time?”</p>
</blockquote>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item><item><title><![CDATA[Setting Up Celery for Production: Isolated Queues, Autoscaling, and Smarter Task Management]]></title><description><![CDATA[When deploying Celery in production, using the default configuration can quickly become a bottleneck. It’s tempting to spin up a few workers with a fixed concurrency and assume the job is done. But as the number of background tasks grows, so do the c...]]></description><link>https://blog.mostafade.com/setting-up-celery-for-production-isolated-queues-autoscaling-and-smarter-task-management</link><guid isPermaLink="true">https://blog.mostafade.com/setting-up-celery-for-production-isolated-queues-autoscaling-and-smarter-task-management</guid><category><![CDATA[celery]]></category><category><![CDATA[deployment]]></category><category><![CDATA[configuration]]></category><category><![CDATA[scale]]></category><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Tue, 20 May 2025 16:44:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747759603963/20c9f17d-8173-49a7-af8f-a8129ac54401.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When deploying Celery in production, using the default configuration can quickly become a bottleneck. It’s tempting to spin up a few workers with a fixed concurrency and assume the job is done. But as the number of background tasks grows, so do the challenges: blocked queues, resource spikes, and unpredictable performance. In this article, I’ll walk you through how I structured Celery for production, focusing on queue isolation, autoscaling, and smarter load handling.</p>
<h3 id="heading-the-default-celery-setup">The Default Celery Setup</h3>
<p>Most tutorials show you how to get Celery running with a single worker and a shared queue. That works for a while. But in practice, this is what you're really getting:</p>
<ul>
<li><p>One or two Celery workers with a fixed number of processes</p>
</li>
<li><p>Tasks from <strong>all</strong> queues are being pulled in a round-robin fashion</p>
</li>
<li><p>No prioritization, critical tasks, and background jobs are treated the same</p>
</li>
</ul>
<p>This might be fine at the beginning. Tasks get processed, and everything seems okay. However, once your application grows and starts generating hundreds or thousands of background jobs, problems appear fast.</p>
<p>The fixed workers continue to pull from all queues, blindly, regardless of task importance. If you suddenly have a spike in long-running tasks like reports, it can block urgent operations like order processing or payment confirmation. Your most critical tasks now have to wait in line behind non-essential ones, and that’s a problem.</p>
<p>Worse, if all tasks are routed into just one or two queues and those queues feed into a single shared worker, there’s no way to prioritize one over the other. In this kind of setup, your users will feel the impact. Delays become visible, responsiveness drops, and user satisfaction, one of the top priorities for any software engineer, starts to suffer.</p>
<p>This is exactly the kind of setup I started with, and exactly what I wanted to improve.</p>
<h3 id="heading-what-i-changed-and-why">What I Changed and Why</h3>
<p>Before diving into the worker configuration, I focused first on getting the queues set up properly. Since all tasks are pushed into queues and workers simply pull from them, it’s essential to ensure that each task lands in the right queue. Think of it this way: we have multiple queues, let’s call them Q1, Q2, Q3, and Q4, and it’s the application’s responsibility to assign tasks to the appropriate ones.</p>
<p>Each queue should be dedicated to a specific type of workload. For example, report-related tasks should go to Q2, background or low-priority tasks to Q3 and Q4, and all other general-purpose tasks to Q1 by default.</p>
<p>With task routing in place, I then moved to the worker configuration. The goal was to ensure that each group of related tasks is processed by a dedicated worker, optimized for the nature and expected volume of tasks in that queue.</p>
<p>I created three separate workers:</p>
<ul>
<li><p><code>default</code>: Handles high-priority operational tasks like (orders, payments, etc...), (tasks in Q1).</p>
</li>
<li><p><code>reports</code>: Reserved for generating large or time-consuming reports (tasks in Q2).</p>
</li>
<li><p><code>others</code>: Manages low-priority or background jobs (e.g., syncing, logs) (tasks in Q3 &amp; Q4).</p>
</li>
</ul>
<p>This separation ensures that no queue can interfere with another. For example, a burst in report generation won’t delay time-sensitive updates from the default queue, and that would make things way faster.</p>
<p>So here is the entire architecture shown in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746888080374/7429e224-2450-4ecc-97de-1c3d2c517e1c.png" alt class="image--center mx-auto" /></p>
<p>Each worker is assigned only the queues it’s responsible for:</p>
<ul>
<li><p><code>default</code> handles only <code>Q1</code></p>
</li>
<li><p><code>reports</code> handles only <code>Q2</code></p>
</li>
<li><p><code>others</code> handles <code>Q3</code> and <code>Q4</code></p>
</li>
</ul>
<p>This explicit routing prevents low-priority queues from blocking more urgent ones and keeps the system predictable.</p>
<p>Now that each worker is assigned to specific queues, it only processes tasks from those queues, and nothing else. But what happens when there are no tasks available for a given worker? Simply put: nothing.</p>
<p>The worker becomes idle, it doesn't consume unnecessary CPU or memory beyond what’s needed to stay alive. This is a huge advantage of queue isolation, each worker is scoped to a known workload, and it's not competing or grabbing tasks from unrelated queues. This keeps the system organized and minimizes unnecessary load.</p>
<p>But there’s more to it. What if you suddenly get a spike in one queue, say a flood of report generation requests? You’d want the system to respond dynamically, without you manually increasing concurrency or restarting workers. That’s exactly where the <code>--autoscale</code> flag becomes valuable.</p>
<p>The <code>--autoscale</code> option allows each worker to scale its concurrency between a minimum and maximum number of worker processes, depending on task volume. In practice, it means Celery will spin up more child processes when there's a queue buildup and scale them back down when demand drops.</p>
<p>Here’s how I configured autoscaling for each worker:</p>
<ul>
<li><p><strong>default</strong>: 1 to 4 concurrent processes</p>
</li>
<li><p><strong>reports</strong>: 1 to 4 concurrent processes</p>
</li>
<li><p><strong>others</strong>: 1 to 2 concurrent processes</p>
</li>
</ul>
<p>This gives each worker enough headroom to handle short-term spikes without wasting resources during quiet periods. Instead of running 4 workers all the time, we start with 1 and let Celery increase concurrency only when needed. It’s a balance between responsiveness and efficiency.</p>
<h3 id="heading-managing-multiple-workers-with-celery-multi">Managing Multiple Workers with <code>celery multi</code></h3>
<p>With queue isolation and autoscaling in place, the next challenge is: how do you manage multiple workers efficiently?</p>
<p>You could run each worker with a separate systemd service file, but that quickly becomes tedious and error-prone. Instead, I opted to use <code>celery multi</code>, which is built specifically for running and controlling multiple named Celery workers as a group. It gives you a way to start, stop, and monitor all your workers together using a single service.</p>
<p>Each worker is defined by name and configured independently through <code>celery multi</code>, including its queues, concurrency, autoscaling settings, and individual PID/log files.</p>
<p>Here’s how I configured this using <code>systemd</code>:</p>
<h3 id="heading-celeryservice"><code>celery.service</code></h3>
<pre><code class="lang-plaintext">[Unit]
Description=Celery Multi-Worker Service
After=network.target

[Service]
Type=forking
User=ubuntu
Group=ubuntu
EnvironmentFile=/etc/conf.d/celery
WorkingDirectory=/&lt;Path to your project&gt;
ExecStart=/bin/bash -c "${CELERY_BIN} -A ${CELERY_APP} multi start default reports others --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} -Q:default Q1 --autoscale:default=4,1 --time-limit:default=3000 -Q:reports Q2 --autoscale:reports=4,1 --time-limit:reports=3000 -Q:others Q3,Q4 --autoscale:others=2,1 --time-limit:others=10000"
ExecStop=/bin/bash -c "${CELERY_BIN} multi stopwait default reports others --pidfile=${CELERYD_PID_FILE}"
ExecReload=/bin/bash -c "${CELERY_BIN} -A ${CELERY_APP} multi restart default reports others --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} -Q:default Q1 --autoscale:default=4,1 --time-limit:default=3000 -Q:reports Q2 --autoscale:reports=4,1 --time-limit:reports=3000 -Q:others Q3,Q4 --autoscale:others=2,1 --time-limit:others=10000"
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
</code></pre>
<hr />
<p>This starts three named workers: <code>default</code>, <code>reports</code>, and <code>others</code>. Each worker is isolated and independently configured:</p>
<ul>
<li><p><code>--pidfile</code> / <code>--logfile</code>: Uses <code>%n</code> in the config file <code>/etc/conf.d/celery</code> to assign a unique file per worker.</p>
</li>
<li><p><code>-Q:&lt;worker name&gt; &lt;queue name&gt;</code>: Each worker is assigned to specific queues.</p>
</li>
<li><p><code>--autoscale:X,Y</code>: Enables dynamic scaling of concurrency between Y (min) and X (max).</p>
</li>
<li><p><code>--time-limit</code>: Sets a hard timeout (in seconds) for task execution. If a task exceeds this time, the worker is killed and the task is marked as failed.</p>
</li>
</ul>
<h3 id="heading-why-this-works-well-in-production">Why This Works Well in Production</h3>
<p>With this setup:</p>
<ul>
<li><p>Each worker has its <strong>own name</strong>, <strong>queue bindings</strong>, <strong>autoscale policy</strong>, and <strong>log/PID files</strong>.</p>
</li>
<li><p>You can manage all workers together using simple systemd commands (<code>start</code>, <code>stop</code>, <code>restart</code>).</p>
</li>
<li><p>You don’t need multiple <code>.service</code> files, which keeps your deployment clean.</p>
</li>
<li><p>It scales seamlessly with your queue design, you can always add another named worker later.</p>
</li>
<li><p>Because you are using one service now, “self-healing“ with <code>Restart=always</code> and <code>RestartSec=5</code> works perfectly, so you will not face any problem in production related to celery not being active for some reason.</p>
</li>
</ul>
<p>This structure not only improves reliability and performance, but also simplifies operational overhead when you need to update or monitor your task workers.</p>
<p>I didn’t want to write yet another article on how to install Celery. Instead, this post dives into the real improvements that make a difference when your system starts scaling. By isolating queues, autoscaling intelligently, and assigning proper limits, Celery becomes a stable and predictable part of your infrastructure.</p>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item><item><title><![CDATA[Tricky Apache & mod_wsgi Issues I’ve Run Into (So You Don’t Have To)]]></title><description><![CDATA[I always say this (Thank god we have default values). They save thousands of developers every day. Imagine if we didn’t have default values that were set based on the common cases, we would face a really hard time doing everything over and over.
But ...]]></description><link>https://blog.mostafade.com/tricky-apache-and-modwsgi-issues-ive-run-into-so-you-dont-have-to</link><guid isPermaLink="true">https://blog.mostafade.com/tricky-apache-and-modwsgi-issues-ive-run-into-so-you-dont-have-to</guid><category><![CDATA[Devops]]></category><category><![CDATA[apache]]></category><category><![CDATA[mod_wsgi]]></category><category><![CDATA[deployment]]></category><category><![CDATA[configuration]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Tue, 29 Apr 2025 18:49:49 GMT</pubDate><content:encoded><![CDATA[<p>I always say this (Thank god we have default values). They save thousands of developers every day. Imagine if we didn’t have default values that were set based on the common cases, we would face a really hard time doing everything over and over.</p>
<p>But not for everything. In my opinion, you should never trust default values. Notice that I didn’t say don't use them; I said don't trust them. You may still use default values, but never blindly trust them. One day, they will fail you if you don't properly understand what you are using.</p>
<p>I've been there and I saw productions used default values for most of the things, but also I saw these values most of the time fail. This is really important to understand, not only for this article topic, but is beyond that. You should never trust default values, especially in the DevOps world, you should always understand what you are configuring and put it under proper testing, otherwise you are just taking unnecessary risks.</p>
<p>With that said, let’s start on our topic for this article</p>
<p>Running <code>mod_wsgi</code> in daemon mode with MPM Event is a great setup, but it’s also easy to misconfigure Apache in ways that tank performance, create stability issues, or even break your application. A lot of these issues come from poorly tuned process/thread settings, misaligned worker configurations, or because you are using the default values.</p>
<p>This article goes over real-world examples of <strong>bad configurations</strong>, <strong>why they fail</strong>, and how to <strong>properly configure Apache and mod_wsgi</strong> for efficient production workloads.</p>
<h2 id="heading-common-misconfigurations-amp-how-to-fix-them"><strong>Common Misconfigurations &amp; How to Fix Them</strong></h2>
<h3 id="heading-misaligned-startservers-maxrequestworkers-threadsperchild-underutilisation-of-resources"><strong>→</strong> Misaligned <code>StartServers</code>, <code>MaxRequestWorkers</code>, <code>ThreadsPerChild</code> Underutilisation of Resources</h3>
<p>A common mistake is setting <code>MaxRequestWorkers</code> too low compared to available worker processes and threads, leaving potential performance on the table.</p>
<pre><code class="lang-apache"><span class="hljs-section">&lt;IfModule mpm_event_module&gt;</span>
    <span class="hljs-attribute">StartServers</span> <span class="hljs-number">3</span>
    <span class="hljs-attribute">MinSpareThreads</span> <span class="hljs-number">25</span>
    <span class="hljs-attribute">MaxSpareThreads</span> <span class="hljs-number">75</span>
    <span class="hljs-attribute">ThreadsPerChild</span> <span class="hljs-number">50</span>
    <span class="hljs-attribute">MaxRequestWorkers</span> <span class="hljs-number">75</span>
<span class="hljs-section">&lt;/IfModule&gt;</span>

<span class="hljs-attribute">WSGIDaemonProcess</span> myapp processes=<span class="hljs-number">4</span> threads=<span class="hljs-number">25</span>
<span class="hljs-attribute">WSGIProcessGroup</span> myapp
</code></pre>
<h3 id="heading-whats-wrong">What's Wrong?</h3>
<ul>
<li><p><code>StartServers</code> used to tell Apache how many workers to create when it first starts, it is good for making sure that it can handle a lot of requests when it starts</p>
</li>
<li><p><code>ThreadsPerChild</code> used to specify how many threads each worker can use to handle requests</p>
<ul>
<li>In simple terms, you can say that each thread eventually will handle one request at a time</li>
</ul>
</li>
<li><p><code>MinSpareThreads</code> and <code>MaxSpareThreads</code> used to specify the min and max of how many threads should be created by Apache and kept for future use.</p>
</li>
<li><p><code>MaxRequestsWorkers</code> The most important config that you need to be careful with once you set, it is used to tell Apache the maximum number of requests it can serve at the same time</p>
<ul>
<li><p>This is used to limit Apache from utilizing the entire resources</p>
</li>
<li><p>When I say (be careful) when setting this, I mean it, and here is why?</p>
</li>
</ul>
</li>
</ul>
<p>When you set <code>StartServers</code> to a specific value, Apache will start creating workers, each worker will start creating threads based on the specified value, but the problem here when you set <code>MaxRequestsWorkers</code> you basically telling Apache how many requests it can handle at once, so if you set it a low number, Apache will see that there are 3 workers and each one can handle 50 requests at a time, so the total is <code>3 × 50 = 150</code> which means when you set <code>75</code> as a maximum, it will start killing threads to reach the limit that you specified which will defiantly will cause problems and issues like (misutilisation, Zombie processes) and so on, so be careful.</p>
<p><code>MaxRequestWorkers (75)</code> <strong>too low</strong>. MPM Event can handle <code>3 processes × 50 threads = 150 requests</code>, but it's capped at 75.</p>
<p><strong>Wasted capacity</strong>: Apache can handle more concurrent requests, but it's artificially restricted.</p>
<h3 id="heading-to-fix-this-align-settings-for-proper-utilization">→ To Fix This: Align Settings for Proper Utilization</h3>
<pre><code class="lang-apache"><span class="hljs-section">&lt;IfModule mpm_event_module&gt;</span>
    <span class="hljs-attribute">StartServers</span> <span class="hljs-number">3</span>
    <span class="hljs-attribute">MinSpareThreads</span> <span class="hljs-number">25</span>
    <span class="hljs-attribute">MaxSpareThreads</span> <span class="hljs-number">75</span>
    <span class="hljs-attribute">ThreadsPerChild</span> <span class="hljs-number">50</span>
    <span class="hljs-attribute">MaxRequestWorkers</span> <span class="hljs-number">150</span>  # Matches <span class="hljs-number">3</span> processes × <span class="hljs-number">50</span> threads
<span class="hljs-section">&lt;/IfModule&gt;</span>

<span class="hljs-attribute">WSGIDaemonProcess</span> myapp processes=<span class="hljs-number">3</span> threads=<span class="hljs-number">50</span>  # Matches MPM Event
<span class="hljs-attribute">WSGIProcessGroup</span> myapp
</code></pre>
<hr />
<h3 id="heading-not-setting-processes-for-modwsgi-blocking-requests-due-to-slow-processing"><strong>→</strong> <strong>Not Setting</strong> <code>--processes</code> <strong>For mod_wsgi,</strong> blocking Requests Due to Slow Processing</h3>
<p>Running Apache in Daemon mode means it acts as a proxy, which means the requests are forwarded to WSGI to be handled on the application level, so imagine you have a slow application with huge requests to process, that means you have slowness, requests being dropped, and in most cases outage.</p>
<p>One way to mitigate this is to increase the number of servers to handle requests, but that’s not helpful if your server is not utilised 100%. Imagine this scenario:</p>
<ul>
<li><p>You have a server with 4 cores and 16 RAM, this server is considered mid-level, which is really good for most applications</p>
<ul>
<li><p>But if you have 1 application running on that server and that is processing everything coming from Apache, at some point, things will get delayed, and your application can’t follow up with Apache</p>
</li>
<li><p>In cases like this, you can just add another server to help, but what about the cost? Why pay money for something that you can easily avoid? You are not fully utilising the server that you already have, so instead of adding servers, why not try to fully utilise what we have instead</p>
</li>
</ul>
</li>
<li><p>This is my argument, you should always try to figure out a way to get use of what you already have instead of introducing something new to the equation.</p>
</li>
</ul>
<hr />
<h3 id="heading-missing-or-incorrect-listen-backlog-too-many-dropped-requests"><strong>→</strong> Missing or incorrect <code>listen-backlog</code>, too Many Dropped Requests</h3>
<p>By default, mod_wsgi <strong>sets</strong> <code>listen-backlog</code> to 128 by default, which means dropped connections during traffic spikes. I saw many production configs not using this, although to be honest it is not that big of deal, the default is already 128 which is fine in most cases specially if you production uses 2 servers that are running all the time, but in case where you app is running on only one server I would recommend to increase it to a higher number. I won’t know the exact number to set, as always, this depends on your app and resources and so on. With that said, I would recommend not setting a really high number as this could make things worst during spikes if your application is slow, so find what number work with your app.</p>
<h3 id="heading-to-fix-increase-listen-backlog">→ To Fix: Increase <code>listen-backlog</code></h3>
<pre><code class="lang-apache"><span class="hljs-attribute">WSGIDaemonProcess</span> myapp processes=<span class="hljs-number">4</span> threads=<span class="hljs-number">25</span> listen-backlog=<span class="hljs-number">500</span>
<span class="hljs-attribute">WSGIProcessGroup</span> myapp
</code></pre>
<ul>
<li>Setting <code>listen-backlog</code> to 500 ensures that <strong>requests are queued</strong> and aren’t rejected immediately, which helps your app reputation (No one likes to see 502/503 errors or something similar once hitting any application).</li>
</ul>
<hr />
<h3 id="heading-modremoteip-cant-parse-client-ip"><strong>→</strong> <code>mod_remoteip</code> can't parse Client IP</h3>
<p>When using Apache behind an AWS ALB, the <code>X-Forwarded-For</code> header forwarded by the ALB includes both the IP and the port (e.g., <code>203.0.113.1:54321</code>). This causes the <code>mod_remoteip</code> module to fail when trying to parse the real client IP.</p>
<h3 id="heading-whats-wrong-1">What's Wrong?</h3>
<ul>
<li><p><code>mod_remoteip</code> expects just the IP (without the port). When it can't parse the header, it defaults to logging and treating the request as if it came from the <strong>ALB IP</strong>.</p>
</li>
<li><p><strong>All requests appear to originate from the load balancer</strong>, which completely breaks IP-based logic.</p>
</li>
</ul>
<p>And of course, this will lead to:</p>
<ul>
<li><p><strong>Incorrect logging</strong>: All client logs will show the ALB IP, not the actual user IP.</p>
</li>
<li><p><strong>Broken rate limiting</strong>: Tools like <code>mod_evasive</code> or WAFs that rely on accurate IP tracking will see all requests coming from one source (the ALB) and could start blocking <strong>legitimate users</strong>.</p>
</li>
</ul>
<h3 id="heading-to-fix-prevent-alb-from-appending-port">→ To Fix: Prevent ALB from Appending Port</h3>
<p>Modify the ALB configuration and disable port forwarding:</p>
<pre><code class="lang-plaintext">enable_xff_client_port = false
</code></pre>
<p>This ensures <code>X-Forwarded-For</code> contains only the IP. Once the ALB is fixed, make sure Apache knows how to extract the real IP:</p>
<pre><code class="lang-plaintext">RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy &lt;ALB_IP/CIDR&gt;
</code></pre>
<p>This way, Apache replaces the client IP with the value from <code>X-Forwarded-For</code>, but <strong>only if</strong> it came from your trusted ALB. This restores proper logging, WAF accuracy, and rate limiting logic.</p>
<hr />
<h3 id="heading-modremoteip-and-multi-proxy-setups-how-misconfiguration-can-expose-you-to-ip-spoofing"><strong>→</strong> mod_remoteip and Multi-Proxy Setups: How Misconfiguration Can Expose You to IP Spoofing</h3>
<p>When using <code>mod_remoteip</code> with reverse proxies like an ALB, Cloudflare, or NGINX, many setups mistakenly trust whatever is in the <code>X-Forwarded-For</code> header, <strong>without validating who sent it</strong>. This makes it easy for attackers to spoof IPs.</p>
<h3 id="heading-whats-wrong-2">What's Wrong?</h3>
<ul>
<li><p><code>X-Forwarded-For</code> can contain multiple IPs (e.g. <code>client, proxy1, proxy2</code>).</p>
</li>
<li><p>If Apache isn’t explicitly told which proxy IPs it should trust, <strong>it may trust attacker-controlled headers</strong>.</p>
</li>
<li><p>That means your logs, WAF rules, rate limiters (e.g., <code>mod_evasive</code>), and even audit trails might all <strong>log fake IPs</strong>.</p>
</li>
</ul>
<h3 id="heading-to-fix-define-who-you-actually-trust">→ To Fix: Define Who You Actually Trust</h3>
<h4 id="heading-1-use-remoteipheader-and-remoteipinternalproxy-properly">1. Use <code>RemoteIPHeader</code> and <code>RemoteIPInternalProxy</code> properly</h4>
<pre><code class="lang-plaintext">RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8 192.168.0.0/16  # Only trust your internal proxy network
</code></pre>
<ul>
<li><p>This tells Apache: “Only trust this header if the request came from one of these trusted internal proxies.”</p>
</li>
<li><p>If you're using an ALB, make sure to list its <strong>private IP or CIDR block</strong>.</p>
</li>
<li><p>If you're behind multiple layers (e.g., Cloudflare → ALB → Apache), configure this carefully.</p>
</li>
</ul>
<hr />
<h3 id="heading-bonus-tip-use-custom-logging-formats">Bonus Tip: Use Custom Logging Formats</h3>
<p>You’re not stuck with the defaults. You can actually <strong>define your own log formats</strong> and log extra info that’s helpful when debugging.</p>
<pre><code class="lang-apache"><span class="hljs-attribute">LogFormat</span> <span class="hljs-string">"%h %l %u %t \"%r\" %&gt;s %b %{ms}T"</span> custom
<span class="hljs-attribute">CustomLog</span> /var/log/apache<span class="hljs-number">2</span>/access.log custom
</code></pre>
<ul>
<li><p>In the example above, <code>%{ms}T</code> logs the <strong>request duration in milliseconds</strong>—super helpful when tracking performance issues.</p>
</li>
<li><p>You can log headers, cookies, query strings—whatever helps you trace problems.</p>
</li>
</ul>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Apache Worker Types: Prefork, Worker, and Event]]></title><description><![CDATA[When deploying applications with Apache, understanding the differences between worker types is critical. Many developers configure Apache without fully considering the impact of their choice, which can lead to poor performance, resource exhaustion, a...]]></description><link>https://blog.mostafade.com/apache-worker-types</link><guid isPermaLink="true">https://blog.mostafade.com/apache-worker-types</guid><category><![CDATA[apache]]></category><category><![CDATA[wsgi]]></category><category><![CDATA[mod_wsgi]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Tue, 21 Jan 2025 16:44:55 GMT</pubDate><content:encoded><![CDATA[<p>When deploying applications with Apache, understanding the differences between worker types is critical. Many developers configure Apache without fully considering the impact of their choice, which can lead to poor performance, resource exhaustion, and even downtime. In this article, I’ll explain the three main Apache Multi-Processing Module (MPM) worker types (<strong>Prefork</strong>, <strong>Worker</strong>, and <strong>Event</strong>) and share my perspective on why Event is the best choice for most modern setups.</p>
<h2 id="heading-overview-of-apache-worker-types">Overview of Apache Worker Types</h2>
<h3 id="heading-prefork">Prefork</h3>
<p>Prefork is a process-based MPM where each connection is handled by a dedicated process.</p>
<ul>
<li><p><strong>How It Works</strong>: Every request spawns a separate process, each handling one connection at a time.</p>
</li>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Non-threaded, making it ideal for non-thread-safe applications.</p>
</li>
<li><p>Simple to debug and configure.</p>
</li>
</ul>
</li>
<li><p><strong>Drawbackkons</strong>:</p>
<ul>
<li><p>Extremely resource-intensive due to the high memory overhead of each process.</p>
</li>
<li><p>Poor scalability for handling large numbers of simultaneous connections.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737476484310/ecd32123-32d3-4e42-8b21-92b477d5e3be.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-worker">Worker</h3>
<p>Worker is a hybrid MPM that uses threads within processes to handle connections.</p>
<ul>
<li><p><strong>How It Works</strong>: Each process can handle multiple threads, and each thread manages a single connection.</p>
</li>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>More efficient than Prefork, with significantly reduced memory usage.</p>
</li>
<li><p>Requires thread-safe applications and libraries.</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li>Threads are preserved for specific connections, which can lead to inefficiency when connections are idle.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737476497251/a7e929b1-6ff7-4d12-94ea-026359dae54c.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-event">Event</h3>
<p>Event is an improvement over Worker, designed to handle idle connections more efficiently.</p>
<ul>
<li><p><strong>How It Works</strong>: Idle connections (e.g., KeepAlive) are moved to an event queue, freeing up threads to handle new requests.</p>
</li>
<li><p><strong>Pros</strong>:</p>
<ul>
<li><p>Reduces resource usage by avoiding tying up threads for idle connections.</p>
</li>
<li><p>Highly scalable and optimized for modern workloads.</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong>:</p>
<ul>
<li><p>Requires thread-safe applications, similar to Worker.</p>
</li>
<li><p>Configuration can be slightly more complex.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737476513321/bb083eaf-8cfd-4308-8f6d-08b86c874e2d.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-why-i-recommend-event-mode">Why I Recommend Event Mode</h2>
<h3 id="heading-my-experience-with-worker-and-event-modes">My Experience with Worker and Event Modes</h3>
<p>Before Apache 2.4 introduced the Event MPM, Worker was often the default choice. However, Event has since proven to be far superior, particularly when Apache is used as a proxy (e.g., with mod_wsgi for Python applications). The key advantage of Event lies in how it handles idle connections—by freeing up threads for new requests, it minimizes CPU and memory utilization. This makes it ideal for high-traffic environments.</p>
<p>One major issue about Worker mode is how threads are preserved for specific connections, even if those connections are idle. This leads to wasted resources, especially under heavy loads. Event eliminates this inefficiency, making it the clear choice.</p>
<p>Thread safety is often a reason to avoid Worker or Event modes. However, I’ve found that most applications can be adjusted to handle thread safety issues at the application level. Modern frameworks and libraries typically offer thread-safe options or ways to modify behavior to ensure compatibility.</p>
<p>Many developers default to Prefork without realizing the performance overhead it introduces. Using Prefork for high-traffic applications or as a proxy is a recipe for disaster. In my experience, it’s not uncommon to wake up to a crashed server or resource spikes caused by poor scalability with Prefork.</p>
<p>Some assume Worker and Event offer similar benefits, but the difference in handling idle connections is significant. Event is far better suited for scenarios with KeepAlive enabled, as it can efficiently manage idle connections and prevent resource bottlenecks.</p>
<h2 id="heading-choosing-the-right-worker-type">Choosing the Right Worker Type</h2>
<h3 id="heading-when-to-use-each-type">When to Use Each Type</h3>
<ul>
<li><p><strong>Prefork</strong>: Only use Prefork if your application requires non-thread-safe modules and cannot be adjusted to work with threads.</p>
</li>
<li><p><strong>Worker</strong>: Suitable for thread-safe applications in environments where KeepAlive is not heavily used.</p>
</li>
<li><p><strong>Event</strong>: The go-to choice for modern setups, especially when using Apache as a proxy or handling high-traffic applications with KeepAlive enabled.</p>
</li>
</ul>
<p>Always aim for Event mode if your setup allows. It offers the best balance of scalability, resource efficiency, and modern functionality. If thread safety is a concern, look for ways to address it within your application rather than defaulting to Prefork.</p>
<p>In future articles, I’ll dive into configuration and tuning tips for Event mode to help you get the most out of your Apache setup.</p>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item><item><title><![CDATA[Should You Always Use Daemon Mode?]]></title><description><![CDATA[mod_wsgi is a great tool for deploying Python web applications with Apache. While it offers two modes embedded and daemon, understanding how they work is essential before deciding which one to use. Spoiler alert: daemon mode is the clear winner for m...]]></description><link>https://blog.mostafade.com/should-you-always-use-daemon-mode</link><guid isPermaLink="true">https://blog.mostafade.com/should-you-always-use-daemon-mode</guid><category><![CDATA[apache]]></category><category><![CDATA[wsgi]]></category><category><![CDATA[mod_wsgi]]></category><category><![CDATA[deployment]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Tue, 07 Jan 2025 17:49:11 GMT</pubDate><content:encoded><![CDATA[<p>mod_wsgi is a great tool for deploying Python web applications with Apache. While it offers two modes <strong>embedded</strong> and <strong>daemon</strong>, understanding how they work is essential before deciding which one to use. Spoiler alert: daemon mode is the clear winner for most use cases. In this article, we’ll explain both modes, how they operate, and why daemon mode is your best bet for scalable and reliable deployments.</p>
<p>Note sure what WSGI or mod_WSGI mean? check out <a target="_blank" href="https://blog.mostafade.com/understanding-wsgi">Understanding WSGI and mod_wsgi</a></p>
<h3 id="heading-embedded-mode">Embedded Mode</h3>
<p>In <strong>embedded mode</strong>, mod_wsgi runs your Python application directly within Apache’s worker processes. Essentially, the application becomes part of Apache itself, sharing resources and processes.</p>
<h4 id="heading-how-embedded-mode-works">How Embedded Mode Works</h4>
<ul>
<li><p>Apache receives an HTTP request.</p>
</li>
<li><p>The worker process handling the request also loads and executes your Python application code.</p>
</li>
<li><p>The response is returned to the client.</p>
</li>
</ul>
<pre><code class="lang-plaintext">Client -&gt; Apache Worker (Runs Python Application) -&gt; Response to Client
</code></pre>
<ul>
<li><p>Applications run within Apache’s processes.</p>
</li>
<li><p>All applications share the same memory space.</p>
</li>
</ul>
<h3 id="heading-daemon-mode">Daemon Mode</h3>
<p>In <strong>daemon mode</strong>, mod_wsgi spawns dedicated processes to run your Python application. These processes are isolated from Apache’s main worker processes, providing better control and flexibility.</p>
<h4 id="heading-how-daemon-mode-works">How Daemon Mode Works</h4>
<ul>
<li><p>Apache receives an HTTP request.</p>
</li>
<li><p>The request is passed to a separate process managed by mod_wsgi.</p>
</li>
<li><p>The Python application processes the request and returns the response to Apache, which then delivers it to the client.</p>
</li>
</ul>
<pre><code class="lang-plaintext">Client -&gt; Apache -&gt; mod_wsgi Daemon Process (Runs Python Application) -&gt; Response to Client
</code></pre>
<ul>
<li><p>Applications run in isolated processes.</p>
</li>
<li><p>Each application can have its own environment, dependencies, and configuration.</p>
</li>
</ul>
<h2 id="heading-why-you-should-always-use-daemon-mode">Why You Should Always Use Daemon Mode</h2>
<h3 id="heading-the-downsides-of-embedded-mode">The Downsides of Embedded Mode</h3>
<ul>
<li><p>First, Apache handles both HTTP requests and runs the Python application, leading to resource bottlenecks.</p>
</li>
<li><p>Since all Apache workers share the same memory, scaling up for traffic spikes becomes challenging. However, I'm not saying it will not scale but compared with Daemon mode you can scale way better and use fewer resources</p>
</li>
<li><p>Also since the entire app deployed and managed by Apache, any error in your Python application can bring down the entire server.</p>
</li>
</ul>
<h3 id="heading-the-advantages-of-daemon-mode">The Advantages of Daemon Mode</h3>
<ul>
<li><p>(Isolated Environments) Since each application runs in its own process, avoiding conflicts and improving stability.</p>
</li>
<li><p>Because You can control the number of processes and threads independently of Apache this means better Scalability.</p>
</li>
<li><p>Crashes or errors in the application won’t affect Apache’s ability to handle HTTP requests.</p>
</li>
<li><p>Starting with daemon mode ensures you’re ready to scale when needed.</p>
</li>
</ul>
<p>As Graham Dumpleton, the creator of mod_wsgi, puts it: <strong>“Friends don’t let friends use embedded mode.”</strong> Even for small applications, daemon mode sets you up for success.</p>
<h2 id="heading-configuring-modwsgi-for-daemon-mode">Configuring mod_wsgi for Daemon Mode</h2>
<p>Here’s how you can set up daemon mode for your application:</p>
<pre><code class="lang-apache"><span class="hljs-section">&lt;VirtualHost *<span class="hljs-number">:80</span>&gt;</span>
    <span class="hljs-attribute"><span class="hljs-nomarkup">ServerName</span></span> example.com

    <span class="hljs-attribute">WSGIRestrictEmbedded</span> <span class="hljs-literal">On</span>
    <span class="hljs-attribute">WSGIDaemonProcess</span> myapp user=www-data processes=<span class="hljs-number">2</span> threads=<span class="hljs-number">25</span> python-home=/ .../env
    <span class="hljs-attribute">WSGIProcessGroup</span> myapp
    <span class="hljs-attribute">WSGIScriptAlias</span> / /path/to/app.wsgi

    <span class="hljs-section">&lt;Directory /path/to/&gt;</span>
        <span class="hljs-attribute">Require</span> <span class="hljs-literal">all</span> granted
    <span class="hljs-section">&lt;/Directory&gt;</span>
<span class="hljs-section">&lt;/VirtualHost&gt;</span>
</code></pre>
<ul>
<li>I will explain more about this configuration and talk about the best way to set up and tweak Apache and mod_wsgi, but I will leave this for another article</li>
</ul>
<p>Quick tips for you:</p>
<ul>
<li><p>Always keep an eye on CPU and Memory utilization to adjust the number of processes and threads (More about this in another article)</p>
</li>
<li><p>For CPU-intensive tasks, reduce the thread count. For I/O-heavy tasks, increase the number of threads.</p>
</li>
<li><p>Always use a virtual environment to isolate dependencies to avoid conflicts</p>
</li>
</ul>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item><item><title><![CDATA[Understanding WSGI and mod_wsgi: A Simple Guide]]></title><description><![CDATA[Introduction
Deploying Python applications hasn't always been as straightforward as it is today. In the early days, web developers faced significant challenges integrating their Python applications with web servers. There was no standard way for web ...]]></description><link>https://blog.mostafade.com/understanding-wsgi</link><guid isPermaLink="true">https://blog.mostafade.com/understanding-wsgi</guid><category><![CDATA[mod_wsgi]]></category><category><![CDATA[Devops]]></category><category><![CDATA[wsgi]]></category><category><![CDATA[deployment]]></category><category><![CDATA[apache]]></category><dc:creator><![CDATA[Mostafa-DE]]></dc:creator><pubDate>Tue, 24 Dec 2024 22:51:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735921856297/b7ad8b0f-610a-4ea6-acee-d2682ecff3d5.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Deploying Python applications hasn't always been as straightforward as it is today. In the early days, web developers faced significant challenges integrating their Python applications with web servers. There was no standard way for web servers to communicate with Python applications. In this article we are going to discuss some topics such as WSGi/mod_wsgi and see some examples of why deploying Python app is considered a challenge.</p>
<p>Before WSGI, deploying Python web apps was extremely hard and challenging especially because of the limitation of resources, even these days you don't find that many articles related to deployment or DevOps to find anything you need to dig deeper and read a lot of old articles, books, and watch old conferences just to get your head around.</p>
<p>Not to mention, Python itself can be a challenging language to work with. The Global Interpreter Lock (GIL) introduces complexities, and it requires a carefully configured environment to run properly. Add to that the package management and the potential for dependency conflicts. Because of these factors, deploying a Python app can be tricky. If you’re not sure what you’re doing, it could lead to a disaster once your app hits production and starts serving users around the globe.</p>
<h2 id="heading-what-is-wsgi">What is WSGI?</h2>
<p>WSGI stands for (Web Server Gateway Interface). It is essentially a specification that outlines how to implement web server gateways. Remember, it is a specification, not an implementation. When someone mentions that an application is deployed on WSGI, they usually mean it is deployed using a web server implementation like mod_wsgi, Gunicorn, etc.</p>
<p>It's crucial to understand the distinction between implementation and specification. A specification provides the guidelines and rules for implementing something specific, while an implementation is the actual execution of those guidelines.</p>
<h2 id="heading-what-is-modwsgi">What is mod_wsgi?</h2>
<p>mod_wsgi is an Apache module that implements the WSGI specification, enabling seamless deployment of Python applications on Apache web servers.</p>
<h3 id="heading-why-modwsgi-became-popular">Why mod_wsgi Became Popular</h3>
<ul>
<li><p><strong>Integration with Apache</strong>: Apache was (and still is) a widely used web server. mod_wsgi’s integration with Apache made it a natural choice for deploying Python applications.</p>
</li>
<li><p><strong>Performance</strong>: Compared to others, mod_wsgi offered significant performance improvements by using persistent processes to handle multiple requests.</p>
</li>
</ul>
<h3 id="heading-how-modwsgi-works">How mod_wsgi Works</h3>
<p>At a high level, mod_wsgi acts as a bridge between Apache and your Python application, you can say it acts as a Proxy in the middle (Although I don't like this term as it has a different meaning) but anyway, let's put it in this way:</p>
<ol>
<li><p>Apache receives an HTTP request.</p>
</li>
<li><p>mod_wsgi translates the request into a format that the Python application understands (using the WSGI standard).</p>
</li>
<li><p>The application processes the request and returns a response.</p>
</li>
<li><p>mod_wsgi sends the response back to Apache, which delivers it to the client.</p>
</li>
</ol>
<p>Although this way of interaction is specific to the daemon mode, we will discuess the different types of modes in mod_wsgi later in a different article but for now let's just stick with the daemon mode.</p>
<p>That's all for now. If you're interested in more advanced content about WSGI, Apache, or other DevOps topics, there are many articles in the DevOps series that you might find interesting.</p>
<p>Mostafa-DE Fayyad</p>
<p>Software Engineer</p>
]]></content:encoded></item></channel></rss>