<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham Singh</title>
    <description>The latest articles on DEV Community by Shubham Singh (@shubhamdevops).</description>
    <link>https://dev.to/shubhamdevops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4058328%2F8cd75a48-d080-45a1-814d-a41965db5568.png</url>
      <title>DEV Community: Shubham Singh</title>
      <link>https://dev.to/shubhamdevops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubhamdevops"/>
    <language>en</language>
    <item>
      <title>How the Web Actually Works: A Developer's Mental Model of Modern Architecture</title>
      <dc:creator>Shubham Singh</dc:creator>
      <pubDate>Tue, 29 Sep 2026 19:06:26 +0000</pubDate>
      <link>https://dev.to/shubhamdevops/how-the-web-actually-works-a-developers-mental-model-of-modern-architecture-ie3</link>
      <guid>https://dev.to/shubhamdevops/how-the-web-actually-works-a-developers-mental-model-of-modern-architecture-ie3</guid>
      <description>&lt;p&gt;I spent a long time writing code before I genuinely understood what happened after I hit Enter on a URL. I knew about frontend and backend. I had a vague sense that CDNs existed. But if someone asked me to draw the path a request takes from a browser to a database and back — I would have drawn something embarrassingly incomplete.&lt;/p&gt;

&lt;p&gt;This post is the explanation I wish I had early on. No Docker. No Kubernetes deep-dives. Just the foundational mental model of how modern web architecture actually fits together — the pieces every developer should be able to reason about clearly.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Actually Happens When You Type a URL
&lt;/h3&gt;

&lt;p&gt;Let us start with the most fundamental question: you open a browser, type &lt;code&gt;https://example.com&lt;/code&gt;, and press Enter. What happens?&lt;/p&gt;

&lt;p&gt;Most people say "the browser loads the page." That is true in the same way that "a car moves" describes a Formula 1 race. The real answer involves at least six different systems activating in under a second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — DNS Resolution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your browser does not understand names like &lt;code&gt;example.com&lt;/code&gt;. It only speaks in IP addresses — things like &lt;code&gt;104.21.5.8&lt;/code&gt;. So the first thing it does is ask a system called DNS (Domain Name System) to translate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser:  "Where is example.com?"
DNS:      "It is at 104.21.5.8"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DNS is essentially a phone book for the internet. The key thing to understand is that the IP address DNS returns is usually &lt;em&gt;not&lt;/em&gt; your actual application server. It almost always belongs to a CDN, a load balancer, or a reverse proxy sitting in front of your real infrastructure. More on those shortly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — HTTPS and Encryption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the browser has an IP, it does not just start firing data at it. It first sets up an encrypted channel using TLS (the protocol behind HTTPS). Think of this as sealing your request in an envelope that only the destination server can open.&lt;/p&gt;

&lt;p&gt;Without this encryption, anyone on the same network — say, someone on the same coffee shop WiFi — could read everything you send: passwords, session cookies, tokens, form data. HTTPS makes the entire conversation private.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — The Edge Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is something most tutorials skip over. Your request does not go straight to your app. It hits what is called the &lt;strong&gt;edge layer&lt;/strong&gt; first — a set of systems sitting between the public internet and your actual servers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;CDN&lt;/strong&gt; (Cloudflare, CloudFront)&lt;/td&gt;
&lt;td&gt;Caches files near the user, absorbs load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spreads traffic across multiple servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reverse Proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Routes requests, hides backend infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WAF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks malicious traffic before it reaches your app&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This layer is doing real work: blocking attack traffic, serving cached files without ever touching your application, routing requests to the right place, and encrypting/decrypting connections. By the time a request actually reaches your application, it has already been filtered and directed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps 4–6 — Application, Database, Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The request finally reaches your application server, which processes it: checks if you are logged in, queries a database, applies business logic, and assembles a response. That response — HTML, CSS, JavaScript, or JSON — travels back the same chain to your browser, which renders what you see.&lt;/p&gt;

&lt;p&gt;The full journey:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You type example.com
       │
       ▼
DNS  →  IP address returned
       │
       ▼
HTTPS connection opened (encrypted)
       │
       ▼
Edge layer (CDN / Load Balancer / Reverse Proxy)
       │
       ▼
Application server
       │
       ▼
Database (if needed)
       │
       ▼
Response travels back
       │
       ▼
Browser renders the page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of that happens in roughly 50–200 milliseconds. Every step matters.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Most Important Split: Frontend vs Backend
&lt;/h3&gt;

&lt;p&gt;Every web system is divided into two distinct worlds, and confusing them causes real architectural mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt; is everything the user sees and interacts with — buttons, layouts, forms, animations. In modern development, frontend code (React, Vue, Angular) runs &lt;em&gt;inside the user's browser&lt;/em&gt;, not on your server. Once the browser downloads your JavaScript bundle, it executes locally on the user's device. Your server is no longer involved in rendering the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt; is everything that runs on your server, hidden from users. It handles things that require authority and trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticating who you are&lt;/li&gt;
&lt;li&gt;Checking what you are allowed to do&lt;/li&gt;
&lt;li&gt;Reading and writing to databases&lt;/li&gt;
&lt;li&gt;Processing payments&lt;/li&gt;
&lt;li&gt;Sending emails&lt;/li&gt;
&lt;li&gt;Enforcing business rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single most important distinction between these two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend:  Runs in the USER's browser — never trust it with secrets
Backend:   Runs on YOUR server — this is where authority lives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just a conceptual distinction. It has real security consequences. Frontend code can be read by anyone — open DevTools, open the source, everything is visible. Sensitive logic, secret keys, and authorization decisions must always live on the backend. The frontend presents information; the backend enforces rules.&lt;/p&gt;




&lt;h3&gt;
  
  
  Static vs Dynamic Content
&lt;/h3&gt;

&lt;p&gt;Not all content on the web is the same kind of thing, and understanding the difference shapes every hosting decision you make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static content&lt;/strong&gt; is identical for every single user who requests it. Your JavaScript bundle, CSS files, images, fonts — these files do not change based on who is asking. They are the same for a first-time visitor in Delhi and a returning user in Berlin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic content&lt;/strong&gt; is generated fresh for each request, personalised per user. Your dashboard data, account balance, notification count, order history — this content is unique and cannot be pre-baked.&lt;/p&gt;

&lt;p&gt;Why does this matter? Because the strategies for serving them are completely different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Static content  →  Serve from CDN (fast, cheap, globally distributed)
Dynamic content →  Must go through your backend server (personalised, cannot be cached naively)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smart architecture offloads as much as possible to static delivery and keeps the backend focused only on work that genuinely requires computation or personalisation.&lt;/p&gt;




&lt;h3&gt;
  
  
  SPA vs SSR: Two Different Ways to Build a Frontend
&lt;/h3&gt;

&lt;p&gt;There are two dominant approaches to building web frontends, and they have meaningfully different architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPA — Single Page Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A SPA (React, Vue in standard mode) works like this: the browser downloads the entire frontend application once — HTML, CSS, a JavaScript bundle — and React boots up inside the browser. From that point, all navigation happens inside the browser. When data is needed, React calls your backend API, gets JSON back, and updates the page without a full reload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First visit:
  Browser → Downloads React app (index.html + main.js)

All subsequent interactions:
  React (in browser) → calls API → backend returns JSON → React updates UI
  No full page reloads. The server's job is essentially done after the initial download.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The big upside: smooth, app-like experience. The static files sit perfectly on a CDN. Frontend and backend are completely independent.&lt;/p&gt;

&lt;p&gt;The known tradeoffs: the first load requires the browser to download and execute JavaScript before anything renders, which can feel slow. Search engines historically struggled with pages that were empty HTML shells populated only after JavaScript ran — though this has improved significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSR — Server-Side Rendering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SSR (Next.js, Nuxt) addresses those tradeoffs by doing the rendering work on the server before sending content to the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser requests /product/123
Server runs React, fetches product data, generates complete HTML
Browser receives ready-to-display HTML immediately
JavaScript loads in background and makes page interactive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user sees real content almost immediately. Search engine crawlers see actual content, not an empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&lt;/code&gt;. The tradeoff is that SSR requires a running server — you cannot just put it on a CDN and walk away.&lt;/p&gt;

&lt;p&gt;Most modern frameworks like Next.js support both modes at once: some pages pre-rendered at build time, others server-rendered on each request, others rendered purely in the browser. You pick per page.&lt;/p&gt;




&lt;h3&gt;
  
  
  API-Driven Architecture: Why Frontend and Backend Are Separate
&lt;/h3&gt;

&lt;p&gt;Older web applications were tightly coupled — the server generated complete HTML pages and sent them to the browser. Modern systems separate frontend and backend into independent services that communicate through APIs.&lt;/p&gt;

&lt;p&gt;An API (Application Programming Interface) is simply a set of URLs your backend exposes that return structured data, almost always JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend calls:  GET /api/user/42
Backend returns: { "id": 42, "name": "Priya", "email": "priya@example.com" }
Frontend renders: a profile card with that data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation pays dividends in multiple ways. The same backend API serves your web frontend, your iOS app, your Android app, and any third-party integrations — all consuming the same endpoints. Frontend teams deploy independently of backend teams. You can rewrite the frontend in a completely different framework without touching the backend.&lt;/p&gt;

&lt;p&gt;The principle here is &lt;strong&gt;loose coupling&lt;/strong&gt; — components that communicate through clean interfaces rather than being tangled together. It is one of the most important architectural patterns in modern software.&lt;/p&gt;




&lt;h3&gt;
  
  
  CDN: Why the Web Can Be Fast for Everyone
&lt;/h3&gt;

&lt;p&gt;Without a CDN, every user in the world downloads files from your one server. A user in Frankfurt downloading your JavaScript from a server in Mumbai is adding 150–200ms of network round-trip latency to every asset, before their browser even starts executing anything.&lt;/p&gt;

&lt;p&gt;A CDN (Content Delivery Network) solves this by maintaining hundreds of servers — called edge nodes — distributed globally. When you upload your static files to a CDN, it replicates them across these locations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without CDN:
  User in Frankfurt → Server in Mumbai → 180ms just in network travel

With CDN:
  User in Frankfurt → CDN edge in Frankfurt → 5ms
  User in Delhi     → CDN edge in Delhi     → 3ms
  User in New York  → CDN edge in New York  → 4ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time a user in Frankfurt requests your &lt;code&gt;main.js&lt;/code&gt;, the CDN fetches it from your origin server and caches it locally. Every user after that — from Frankfurt or anywhere nearby — gets it directly from that edge node. Your origin server is not involved at all.&lt;/p&gt;

&lt;p&gt;This is what "cache hit" means: the file was already at the nearest edge. At scale, a well-configured CDN will serve 95%+ of static file requests from cache, leaving your origin server mostly untouched.&lt;/p&gt;

&lt;p&gt;One clarification worth making explicit: CDNs are excellent at serving static content. They are not designed for your dynamic API responses that contain personal user data. Those must still reach your backend.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reverse Proxies: The Traffic Directors
&lt;/h3&gt;

&lt;p&gt;A reverse proxy is a server that sits in front of your application and mediates all incoming traffic. Users talk to the proxy; the proxy talks to your app. Users never contact your application directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → Reverse Proxy → Your Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx is the most common reverse proxy, and it earns its place by handling several things your application should not have to deal with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLS termination&lt;/strong&gt; — the reverse proxy handles HTTPS decryption so your application code receives plain HTTP. You manage one certificate, at the proxy, not inside every service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing&lt;/strong&gt; — requests to &lt;code&gt;/api/*&lt;/code&gt; go to the backend server, requests to &lt;code&gt;/&lt;/code&gt; go to the frontend server. One proxy, multiple destinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compression&lt;/strong&gt; — the proxy compresses responses automatically. A 500KB JSON payload can compress to 80KB, reducing transfer time by 84%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; — block clients making too many requests before they ever touch your application code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buffering&lt;/strong&gt; — this one is subtle but important. Without buffering, slow mobile clients keep Gunicorn workers busy for the entire duration of the download, even though Django finished generating the response in 10ms. With buffering, the proxy receives the full response instantly, frees the Gunicorn worker, and then slowly drips data to the slow client. Your application becomes far more concurrent.&lt;/p&gt;




&lt;h3&gt;
  
  
  Load Balancers: Distributing the Work
&lt;/h3&gt;

&lt;p&gt;When traffic grows beyond what one server can handle, you run multiple copies of your application and use a load balancer to distribute requests across them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users
  │
  ▼
Load Balancer
  ├── Server 1
  ├── Server 2
  └── Server 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The load balancer tracks which servers are healthy and routes new requests to available ones. If Server 2 goes down, the load balancer stops sending it traffic automatically. When it recovers, it is added back.&lt;/p&gt;

&lt;p&gt;This setup also enables something powerful: you can take servers offline for updates without any downtime. Take Server 1 offline, update it, bring it back, then do the same for Server 2. Traffic continues flowing to the remaining healthy servers throughout.&lt;/p&gt;

&lt;p&gt;Load balancers are also the entry point for autoscaling. At low traffic: two servers. Traffic spikes: add four more. Load balancer starts including them immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stateless vs Stateful: The Architectural Divide That Shapes Everything
&lt;/h3&gt;

&lt;p&gt;This concept shows up in every conversation about scaling, and it is worth understanding precisely.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;stateless&lt;/strong&gt; system stores no important data locally. Every request it receives is self-contained — the server does not need to remember anything about previous requests to handle the next one. If you have three copies of a stateless backend running, any of them can handle any request. Kill one, start another, it picks up immediately.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;stateful&lt;/strong&gt; system holds critical persistent data. A database is stateful. If your PostgreSQL instance disappears, so does every user record, every order, every piece of data your product is built on. You cannot simply start a fresh copy and continue — the data is gone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stateless → Can restart anytime, scale freely, failures are trivial to recover
Stateful  → Requires persistent storage, backups, replication, failover plans
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The golden rule of modern web architecture follows from this: &lt;strong&gt;make your application logic stateless&lt;/strong&gt;. Store all persistent data in dedicated external systems — databases, cache stores, object storage. Your application servers then become disposable: scale them up, scale them down, replace crashed ones, deploy new versions — none of it risks data loss because there is no important data inside them.&lt;/p&gt;

&lt;p&gt;A concrete example: if a user uploads a profile photo and your backend stores it on the server's local filesystem, you have a stateful application. When that server restarts or gets replaced, the photo is gone. If instead the backend uploads the photo to object storage (S3, R2, or similar) and stores only the URL in the database, the server becomes stateless again — it holds nothing of value that cannot be recovered.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Three-Layer Production Architecture
&lt;/h3&gt;

&lt;p&gt;Modern production systems are almost always organised into three distinct layers. Understanding this structure helps you reason about where things live and why.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELIVERY LAYER
  CDN (Cloudflare, CloudFront)
  Serves static files from hundreds of global locations
  React app, CSS, images, fonts

         │
         ▼

COMPUTE LAYER
  Application servers
  Backend APIs, business logic, authentication
  WebSocket services for realtime features
  Background workers for async jobs

         │
         ▼

DATA LAYER
  PostgreSQL (relational data — users, orders, content)
  Redis (fast cache, sessions, realtime pub/sub)
  Object Storage (uploaded files, images, documents)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delivery layer is cheap and globally fast — CDN infrastructure is designed to handle enormous traffic with minimal cost. The compute layer is stateless and scales horizontally — add more servers during peaks, remove them during quiet periods. The data layer is stateful and requires the most careful management — this is where persistence, replication, and backups live.&lt;/p&gt;

&lt;p&gt;Here is what a real request looks like flowing through this:&lt;/p&gt;

&lt;p&gt;A user loads your React app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → CDN edge (nearest city) → serves main.js from cache → React boots up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That user logs in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Cloudflare → Load Balancer → Backend API
        → database query (verify credentials)
        → returns auth token to browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React fetches dashboard data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Load Balancer → Backend API
        → Redis (check cache — is this data already stored?)
        → Cache miss → PostgreSQL query
        → Store result in Redis for next request
        → Return JSON → React renders the page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer does what it is best at. Static files never touch your backend. Dynamic data never hits the CDN. Frequently-accessed data gets cached in Redis so the database is not queried repeatedly for the same thing.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Mental Models That Actually Matter
&lt;/h3&gt;

&lt;p&gt;After understanding all of the above, a few core ideas stand out as the ones worth internalising:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The web is many systems, not one.&lt;/strong&gt; A single URL request involves DNS, TLS negotiation, CDN caching, load balancing, application processing, and database queries. Understanding which layer is responsible for what makes debugging and architecture decisions dramatically clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend and backend are separated by design.&lt;/strong&gt; The browser runs your UI. Your server runs your logic. Never put secrets or authorization decisions in the frontend — it is visible to everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static content and dynamic content need different strategies.&lt;/strong&gt; Files that are the same for everyone belong on a CDN. Data that is personalised or changes frequently must be generated by the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateless scales, stateful requires care.&lt;/strong&gt; Design your application servers to hold no important data. Let databases and dedicated storage systems handle persistence. This is not just good practice — it is what makes modern scaling possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CDN is not optional at scale.&lt;/strong&gt; Serving files from one server to users on every continent is slow and expensive. CDNs exist to solve this, and they solve it well.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Comes Next
&lt;/h3&gt;

&lt;p&gt;This is the foundation. The natural next questions — how are applications packaged and deployed, how do they handle thousands of simultaneous users, how does authentication work in distributed systems, how do you build real-time features — all of them build directly on the concepts here.&lt;/p&gt;

&lt;p&gt;Once you can picture the full request journey, the layers of a production system, and why stateless design matters, the rest of modern web infrastructure becomes considerably less mysterious.&lt;/p&gt;

&lt;p&gt;The concepts have names: containers, orchestration, JWT, WebSockets, message queues. But underneath the vocabulary, they are all answers to the same fundamental questions: how do you serve the right content to the right user, reliably, at scale, without losing data? The architecture described here is how the industry answered those questions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a Privacy Layer for AI Coding Tools — CodeMask UI and CodeMask Proxy</title>
      <dc:creator>Shubham Singh</dc:creator>
      <pubDate>Wed, 19 Aug 2026 15:19:59 +0000</pubDate>
      <link>https://dev.to/shubhamdevops/i-built-a-privacy-layer-for-ai-coding-tools-codemask-ui-and-codemask-proxy-3ne6</link>
      <guid>https://dev.to/shubhamdevops/i-built-a-privacy-layer-for-ai-coding-tools-codemask-ui-and-codemask-proxy-3ne6</guid>
      <description>&lt;p&gt;I was debugging a config file of my personal project and pasted it straight into Claude to get help. A few seconds later I looked at what I had actually sent: internal IP addresses, a database password, and an API key. All of it. To a cloud API. Without thinking.&lt;/p&gt;

&lt;p&gt;I don't think I am the only one who has done this. Most developers use AI assistants daily, and most of the time the code going in contains things it shouldn't. This post is about the two tools I built to fix that — and what I learned building them.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem — Two Different Workflows
&lt;/h3&gt;

&lt;p&gt;Before building anything, I realized the problem has two distinct shapes depending on how you use AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow 1 — Manual AI chat (ChatGPT, Claude.ai, Gemini)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You copy a file, paste it into the chat, ask your question, copy the response. The sensitive data goes in with the code because you are doing the pasting manually and there is no gate between your clipboard and the AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow 2 — AI coding agents (Cline, Claude Code, Cursor)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These tools read your files automatically. When you ask Cline to explain a function, it reads the file, builds a prompt, and calls the LLM API directly. There is no paste step at all. Your secrets go out in the background with every request without you ever touching them.&lt;/p&gt;

&lt;p&gt;Same root problem, completely different mechanism. Each needed a different solution.&lt;/p&gt;




&lt;h3&gt;
  
  
  Solution 1 — CodeMask UI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A browser-based tool that sanitizes code before you share it with any AI chat, and restores real values after the AI responds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; Two-way flow with a session registry.&lt;/p&gt;

&lt;p&gt;When you paste code in and click Sanitize, the tool scans for secrets using regex patterns and replaces each one with a numbered placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_HOST = "192.168.50.100"     →    DB_HOST = "{{IP_1}}"
DB_PASSWORD = "SuperSecret123" →    DB_PASSWORD = "{{PASSWORD_1}}"
API_KEY = "sk-test-abc123..."  →    API_KEY = "{{OPENAI_KEY_1}}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A registry is built in the background mapping every placeholder to its real value. You copy the sanitized code, paste it into Claude or ChatGPT, and ask your question. When the AI responds with suggestions that reference &lt;code&gt;{{IP_1}}&lt;/code&gt;, you paste that response back into the Restore tab. The tool looks up the registry and swaps every placeholder back to the real value before you see it.&lt;/p&gt;

&lt;p&gt;The registry persists in &lt;code&gt;localStorage&lt;/code&gt; so it survives a page refresh. If you sanitize three different code files in one session, the same IP always gets the same placeholder, and all three can be restored from the same registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it detects automatically:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IPv4 / CIDR&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;10.10.10.10&lt;/code&gt;, &lt;code&gt;192.168.1.0/24&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPv6&lt;/td&gt;
&lt;td&gt;Full and compressed formats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AKIA...&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sk-...&lt;/code&gt;, &lt;code&gt;sk-proj-...&lt;/code&gt;, &lt;code&gt;sk_test_...&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Keys&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sk-ant-...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Tokens&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ghp_...&lt;/code&gt;, &lt;code&gt;gho_...&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab Tokens&lt;/td&gt;
&lt;td&gt;&lt;code&gt;glpat-...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack Tokens&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;xoxb-...&lt;/code&gt;, &lt;code&gt;xoxp-...&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bearer Tokens&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Authorization: Bearer ...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic API Keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;api_key = "..."&lt;/code&gt;, &lt;code&gt;token: "..."&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JWT / Signing Keys&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jwt_signing_key = "..."&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passwords (quoted)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;password = "secret"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passwords (unquoted)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PASSWORD=Hello123&lt;/code&gt;, &lt;code&gt;db_password=secret&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB Connection Strings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;postgres://user:pass@host&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PEM Private Keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-----BEGIN PRIVATE KEY-----&lt;/code&gt; blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Architecture — nothing leaves the browser:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is built with React and Vite. There is no backend, no server, no API calls. The entire sanitize and restore logic runs client-side in the browser. Your secrets never travel anywhere — not even to a server I control. The &lt;code&gt;patterns.js&lt;/code&gt; file holds all the detection regexes and &lt;code&gt;sanitizer.js&lt;/code&gt; handles the replace and restore logic. Both are plain JavaScript, readable, and extendable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;codemask/
├── src/
│   ├── engine/
│   │   ├── patterns.js       ← detection regexes (add your own here)
│   │   └── sanitizer.js      ← sanitize + restore logic
│   ├── hooks/
│   │   ├── useRegistry.js    ← session state + localStorage persistence
│   │   └── useCopy.js        ← clipboard hook
│   ├── components/
│   │   ├── CodePanel.jsx     ← input/output panels
│   │   ├── RegistryTable.jsx ← secrets table with blur/reveal
│   │   └── ManualAdd.jsx     ← manual secret registration
│   └── App.jsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Running it locally:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/shubham-singhS2/CodeMask.git
&lt;span class="nb"&gt;cd &lt;/span&gt;CodeMask
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;span class="c"&gt;# Open http://localhost:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 shubhamsinghs2/codemask:latest
&lt;span class="c"&gt;# Open http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One thing worth mentioning:&lt;/strong&gt; CIDR notation is handled properly. &lt;code&gt;192.168.1.0/24&lt;/code&gt; becomes &lt;code&gt;{{IP_1}}/24&lt;/code&gt; not &lt;code&gt;{{IP_1}}&lt;/code&gt; — the prefix is preserved so the AI still understands it is a network range, not just a host address.&lt;/p&gt;




&lt;h3&gt;
  
  
  Solution 2 — CodeMask Proxy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem with the UI tool for agent workflows:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you use Cline or Claude Code, the agent reads your project files and sends them to the LLM API automatically. There is no paste step, so there is no place to intercept manually. By the time you see any output, your secrets have already been sent.&lt;/p&gt;

&lt;p&gt;The fix is a proxy server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A local Node.js/Express server that runs on &lt;code&gt;localhost:8080&lt;/code&gt; and acts as a drop-in replacement for any OpenAI-compatible LLM API endpoint.&lt;/p&gt;

&lt;p&gt;You change one setting in your AI agent — point the base URL to &lt;code&gt;localhost:8080&lt;/code&gt; instead of &lt;code&gt;api.openai.com&lt;/code&gt; or &lt;code&gt;api.mistral.ai&lt;/code&gt;. The agent never knows the difference. Every request passes through the proxy first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The complete flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent (Cline)
      │
      │  POST /v1/chat/completions
      │  { messages: [{ content: "DB=192.168.1.10 password=secret" }] }
      ▼
CodeMask Proxy (localhost:8080)
      │  scans all message content for secrets
      │  builds session registry
      │  replaces real values with placeholders
      │
      │  POST /v1/chat/completions  ← forwarded to real API
      │  { messages: [{ content: "DB={{IP_1}} password={{PASSWORD_1}}" }] }
      ▼
Real LLM API (Mistral / OpenAI / your org LLM)
      │
      │  response: "The config connects to {{IP_1}} using {{PASSWORD_1}}"
      ▼
CodeMask Proxy
      │  scans response for placeholders
      │  restores real values from session registry
      │
      │  response: "The config connects to 192.168.1.10 using secret"
      ▼
AI Agent — receives real values, works normally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Proof from a real test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I asked Cline to explain a &lt;code&gt;config.py&lt;/code&gt; file containing real IPs and passwords while the proxy was running. This is what the proxy logged as going to Mistral:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[VERIFY] ── What proxy sent to LLM ──────────────────
[read_file for 'config.py'] Result:
1 | DB_HOST = "{{IP_1}}"
2 | DB_PASSWORD = "{{PASSWORD_1}}"
3 | API_KEY = "{{OPENAI_KEY_1}}"
[VERIFY] ─────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mistral never saw a single real value. Cline received the response with real values fully restored and worked normally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session management — in-memory with TTL:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The registry that maps &lt;code&gt;{{IP_1}}&lt;/code&gt; back to &lt;code&gt;192.168.1.10&lt;/code&gt; lives in server memory, not a database or file. Each agent gets a session (derived from its API key), and sessions expire automatically after 60 minutes of inactivity. A cleanup timer runs every 10 minutes. If the proxy restarts, sessions clear — which is intentional. There is no sensitive data persisted anywhere on disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Each session holds:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto-abc123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{IP_1}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;realValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;192.168.1.10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{PASSWORD_1}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;realValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;counters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;lastUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;expiresInSec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3450&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Streaming — the tricky part:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LLM APIs stream responses as Server-Sent Events (SSE). Each chunk is a small JSON object with a few tokens of content. The challenge: a placeholder like &lt;code&gt;{{IP_1}}&lt;/code&gt; can arrive split across two chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chunk 1: "connect to {{IP"
Chunk 2: "_1}} and use port 5432"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-chunk restoration fails because neither chunk contains the complete placeholder. The fix: buffer the entire stream from the LLM, restore placeholders on the complete assembled text, then re-emit the restored content as fresh SSE chunks back to the agent. The agent gets a streaming response. The placeholders are restored correctly. Both requirements satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/shubham-singhS2/codemask-proxy.git
&lt;span class="nb"&gt;cd &lt;/span&gt;codemask-proxy
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
npm start
&lt;span class="c"&gt;# Proxy running at http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with Docker (recommended for daily use — runs in background, restarts on reboot):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; codemask-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;OPENAI_TARGET_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.mistral.ai/v1 &lt;span class="se"&gt;\&lt;/span&gt;
  shubhamsinghs2/codemask-proxy:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configuring your agent (Cline example):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;API&lt;/span&gt; &lt;span class="py"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenAI Compatible&lt;/span&gt;
&lt;span class="err"&gt;Base&lt;/span&gt; &lt;span class="py"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;http://localhost:8080&lt;/span&gt;
&lt;span class="err"&gt;API&lt;/span&gt; &lt;span class="py"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;your-real-api-key  ← forwarded transparently&lt;/span&gt;
&lt;span class="py"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;mistral-small-latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire setup. One config change and every request is protected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring — endpoints and dashboard:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The proxy exposes management endpoints so you can inspect what is happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Health + global stats&lt;/span&gt;
curl http://localhost:8080/status | jq

&lt;span class="c"&gt;# Active sessions&lt;/span&gt;
curl http://localhost:8080/sessions | jq

&lt;span class="c"&gt;# Full registry for one session (values partially masked in output)&lt;/span&gt;
curl http://localhost:8080/session/SESSION_ID | jq

&lt;span class="c"&gt;# Last 50 requests&lt;/span&gt;
curl http://localhost:8080/logs | jq

&lt;span class="c"&gt;# Clear a session&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://localhost:8080/session/SESSION_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a browser dashboard at &lt;code&gt;http://localhost:8080/dashboard&lt;/code&gt; showing stat cards, session table with expandable registry, and a request log — useful if you prefer a visual view over curl.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider compatibility:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Works&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Standard format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Verified with Cline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/v1/messages&lt;/code&gt; route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groq&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;OPENAI_TARGET_URL=https://api.groq.com/openai&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;OPENAI_TARGET_URL=http://localhost:11434&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Org / internal LLM&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;DISABLE_TLS_VERIFY=true&lt;/code&gt; for self-signed certs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;SigV4 auth not supported yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Different format not supported yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The self-signed certificate case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your organisation runs an internal LLM with a self-signed certificate, Node.js will refuse to connect by default. Rather than setting &lt;code&gt;NODE_TLS_REJECT_UNAUTHORIZED=0&lt;/code&gt; in your terminal every time, you can set it once in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DISABLE_TLS_VERIFY=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy reads this at startup and sets the flag internally. You never have to think about it again.&lt;/p&gt;




&lt;h3&gt;
  
  
  Testing Without a Real API Key
&lt;/h3&gt;

&lt;p&gt;Both tools can be tested completely without spending any API credits using a mock LLM server included in the proxy repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1 — start mock server (pretends to be Mistral/OpenAI)&lt;/span&gt;
node mock-llm.js

&lt;span class="c"&gt;# Terminal 2 — start proxy pointing at mock&lt;/span&gt;
&lt;span class="c"&gt;# Set OPENAI_TARGET_URL=http://localhost:9999 in .env&lt;/span&gt;
npm start

&lt;span class="c"&gt;# Terminal 3 — send a test request&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: fake-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "gpt-4",
    "messages": [{
      "role": "user",
      "content": "DB=192.168.1.10 password=secret123 fix this"
    }]
  }'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mock server prints a security check for every request showing whether it received placeholders or real values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔍 Security Check:
   Placeholders found : ✅ YES
   Real IPs present   : ✅ NO
   Real passwords     : ✅ NO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  How the Two Tools Relate
&lt;/h3&gt;

&lt;p&gt;They solve the same problem at different layers and are designed to be used together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;CodeMask UI&lt;/th&gt;
&lt;th&gt;CodeMask Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual AI chat (ChatGPT, Claude.ai)&lt;/td&gt;
&lt;td&gt;AI coding agents (Cline, Claude Code)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Triggered by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You, manually&lt;/td&gt;
&lt;td&gt;Agent requests, automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser localStorage&lt;/td&gt;
&lt;td&gt;Server memory, TTL-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runs as&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static site (Nginx/Vercel)&lt;/td&gt;
&lt;td&gt;Node.js process or Docker container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open a URL&lt;/td&gt;
&lt;td&gt;One docker run or npm start&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both use the same detection engine — &lt;code&gt;patterns.js&lt;/code&gt; and &lt;code&gt;sanitizer.js&lt;/code&gt; — which is the core logic shared between them.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Is Next
&lt;/h3&gt;

&lt;p&gt;Both projects are deployed and working. The UI version is running on a K3s cluster deployed via ArgoCD using a GitOps pipeline — GitHub Actions builds a Docker image, pushes to Docker Hub, updates the Kubernetes manifests repo, and ArgoCD syncs the cluster automatically. That deployment story is worth a separate post.&lt;/p&gt;

&lt;p&gt;Both are open source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CodeMask UI:&lt;/strong&gt; &lt;a href="https://github.com/shubham-singhS2/CodeMask" rel="noopener noreferrer"&gt;github.com/shubham-singhS2/CodeMask&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeMask Proxy:&lt;/strong&gt; &lt;a href="https://github.com/shubham-singhS2/codemask-proxy" rel="noopener noreferrer"&gt;github.com/shubham-singhS2/codemask-proxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Hub (UI):&lt;/strong&gt; &lt;a href="https://hub.docker.com/r/shubhamsinghs2/codemask" rel="noopener noreferrer"&gt;hub.docker.com/r/shubhamsinghs2/codemask&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Hub (Proxy):&lt;/strong&gt; &lt;a href="https://hub.docker.com/r/shubhamsinghs2/codemask-proxy" rel="noopener noreferrer"&gt;hub.docker.com/r/shubhamsinghs2/codemask-proxy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use AI coding tools and have not thought about what goes into those prompts, it is worth spending ten minutes on. The proxy setup takes less time than that.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claude</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Set Up a Free Custom Domain Email with Zoho Mail, Cloudflare, and Your Own Domain</title>
      <dc:creator>Shubham Singh</dc:creator>
      <pubDate>Sat, 01 Aug 2026 18:29:57 +0000</pubDate>
      <link>https://dev.to/shubhamdevops/how-to-set-up-a-free-custom-domain-email-with-zoho-mail-cloudflare-and-your-own-domain-5dpo</link>
      <guid>https://dev.to/shubhamdevops/how-to-set-up-a-free-custom-domain-email-with-zoho-mail-cloudflare-and-your-own-domain-5dpo</guid>
      <description>&lt;h3&gt;
  
  
  How to Set Up a Free Custom Domain Email with Zoho Mail, Cloudflare, and Your Own Domain
&lt;/h3&gt;

&lt;p&gt;A custom email address like &lt;code&gt;contact@yourdomain.com&lt;/code&gt; makes a huge difference when you are building a personal brand, portfolio, or freelance presence. It looks more professional than a free Gmail address, and it is surprisingly easy to set up using Zoho Mail’s free plan and your domain’s DNS.&lt;/p&gt;

&lt;p&gt;In this guide, I will walk through the exact flow I used to create a professional email address on a custom domain, without paying for a traditional business email suite.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; This post uses placeholder values instead of real DNS records, IPs, or credentials. Replace the examples with the values Zoho shows for your own account.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What you will need
&lt;/h2&gt;

&lt;p&gt;Before you begin, make sure you already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A domain name (for example, &lt;code&gt;yourdomain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;DNS access in Cloudflare or your domain provider&lt;/li&gt;
&lt;li&gt;A Zoho Mail account&lt;/li&gt;
&lt;li&gt;A few minutes to add DNS records and wait for propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this setup, I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website hosting:&lt;/strong&gt; Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS:&lt;/strong&gt; Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email provider:&lt;/strong&gt; Zoho Mail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination works very well for a personal website or portfolio.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why use Zoho Mail?
&lt;/h2&gt;

&lt;p&gt;Zoho Mail is useful because it lets you create a professional email address using your own domain. On the free plan, Zoho supports a single domain with up to 5 users, 5 GB of storage per user, and web-only access. The free plan is available only in select data centers, so availability may vary by region.&lt;/p&gt;

&lt;p&gt;For a personal website, that is usually more than enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The overall flow
&lt;/h2&gt;

&lt;p&gt;Here is the setup in simple terms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buy a domain.&lt;/li&gt;
&lt;li&gt;Sign up for Zoho Mail.&lt;/li&gt;
&lt;li&gt;Add your existing domain.&lt;/li&gt;
&lt;li&gt;Verify that you own the domain using a TXT record.&lt;/li&gt;
&lt;li&gt;Add MX records so mail is delivered to Zoho.&lt;/li&gt;
&lt;li&gt;Add SPF, DKIM, and DMARC for email authentication.&lt;/li&gt;
&lt;li&gt;Test sending and receiving mail.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 1: Sign up for Zoho Mail
&lt;/h2&gt;

&lt;p&gt;Go to Zoho Mail’s signup page and choose the free plan.&lt;/p&gt;

&lt;p&gt;During signup, Zoho will ask whether you want to add a new domain or an existing one. Since you already own the domain, choose &lt;strong&gt;Add an existing domain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Enter your domain exactly as it should appear, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not type &lt;code&gt;www.yourdomain.com&lt;/code&gt; for email setup. Email should be configured on the root domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Verify domain ownership
&lt;/h2&gt;

&lt;p&gt;Zoho will ask you to prove that you own the domain. The simplest method is to add a TXT record in Cloudflare DNS.&lt;/p&gt;

&lt;p&gt;Zoho will give you a TXT value similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zoho-verification=zbXXXXXXXX.zmverify.zoho.in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cloudflare, add a new DNS record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zoho-verification=zbXXXXXXXX.zmverify.zoho.in&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If Cloudflare does not accept &lt;code&gt;@&lt;/code&gt;, use the root domain name directly.&lt;/p&gt;

&lt;p&gt;After saving the record, go back to Zoho and click &lt;strong&gt;Verify TXT Record&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once that works, Zoho knows the domain belongs to you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Create your mailbox
&lt;/h2&gt;

&lt;p&gt;After verification, Zoho will let you create the first user account for your domain.&lt;/p&gt;

&lt;p&gt;For a personal site, a good choice is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contact@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello@yourdomain.com
shubham@yourdomain.com
resume@yourdomain.com
jobs@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only want one inbox, use aliases later instead of creating multiple mailboxes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Add MX records in Cloudflare
&lt;/h2&gt;

&lt;p&gt;MX records tell the internet where your email should be delivered.&lt;/p&gt;

&lt;p&gt;Without MX records, people can send mail to your domain name, but no one will know which server should receive it.&lt;/p&gt;

&lt;p&gt;Add the MX records Zoho provides for your data center. For Zoho’s India region, the records usually look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Mail Server&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;mx.zoho.in&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;mx2.zoho.in&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;mx3.zoho.in&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Cloudflare DNS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type: &lt;code&gt;MX&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;@&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Content: &lt;code&gt;mx.zoho.in&lt;/code&gt; / &lt;code&gt;mx2.zoho.in&lt;/code&gt; / &lt;code&gt;mx3.zoho.in&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Priority: &lt;code&gt;10&lt;/code&gt;, &lt;code&gt;20&lt;/code&gt;, &lt;code&gt;50&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not proxy MX records. Cloudflare handles them as DNS-only records.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Add SPF
&lt;/h2&gt;

&lt;p&gt;SPF tells the world which servers are allowed to send email for your domain.&lt;/p&gt;

&lt;p&gt;Add this TXT record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=spf1 include:zoho.in ~all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cloudflare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=spf1 include:zoho.in ~all&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What SPF means
&lt;/h3&gt;

&lt;p&gt;This says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zoho is allowed to send mail for your domain.&lt;/li&gt;
&lt;li&gt;Other servers should not be trusted by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps reduce spoofing and improves inbox delivery.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Add DKIM
&lt;/h2&gt;

&lt;p&gt;DKIM adds a digital signature to outgoing email. It helps receivers confirm that the message really came from your domain and was not modified in transit.&lt;/p&gt;

&lt;p&gt;Zoho will generate a DKIM public key for you. The record usually looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;zmail._domainkey&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=DKIM1; k=rsa; p=YOUR_LONG_PUBLIC_KEY_HERE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What to do
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open Zoho Admin Console.&lt;/li&gt;
&lt;li&gt;Go to DKIM settings.&lt;/li&gt;
&lt;li&gt;Generate the DKIM record.&lt;/li&gt;
&lt;li&gt;Copy the full TXT value into Cloudflare.&lt;/li&gt;
&lt;li&gt;Save the record.&lt;/li&gt;
&lt;li&gt;Return to Zoho and verify it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DKIM verification can take a little longer than SPF because DNS propagation and verification caches may not update instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Add DMARC
&lt;/h2&gt;

&lt;p&gt;DMARC is the policy layer that sits on top of SPF and DKIM.&lt;/p&gt;

&lt;p&gt;It tells receiving mail servers what to do when authentication fails.&lt;/p&gt;

&lt;p&gt;A good starting DMARC record for a personal domain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=DMARC1; p=quarantine; pct=100; adkim=r; aspf=r; rua=mailto:contact@yourdomain.com; ruf=mailto:contact@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cloudflare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;_dmarc&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=DMARC1; p=quarantine; pct=100; adkim=r; aspf=r; rua=mailto:contact@yourdomain.com; ruf=mailto:contact@yourdomain.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What these parts mean
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p=quarantine&lt;/code&gt; → suspicious messages should go to spam&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pct=100&lt;/code&gt; → apply this policy to all mail&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adkim=r&lt;/code&gt; → relaxed DKIM alignment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aspf=r&lt;/code&gt; → relaxed SPF alignment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rua&lt;/code&gt; → aggregate reports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ruf&lt;/code&gt; → forensic reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most personal domains, relaxed alignment is the simplest and safest choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Wait for propagation
&lt;/h2&gt;

&lt;p&gt;After you add the records, the changes need time to spread across DNS resolvers.&lt;/p&gt;

&lt;p&gt;Usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TXT records propagate quickly&lt;/li&gt;
&lt;li&gt;MX records may take a little longer&lt;/li&gt;
&lt;li&gt;Zoho’s dashboard may still show red or pending status even when everything is already working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not panic if Zoho’s UI is slow to update.&lt;/p&gt;

&lt;p&gt;A good way to confirm is to test real mail flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: Test sending and receiving
&lt;/h2&gt;

&lt;p&gt;Send a test email from your Zoho mailbox to Gmail.&lt;/p&gt;

&lt;p&gt;Then reply from Gmail back to your custom domain email.&lt;/p&gt;

&lt;p&gt;If both directions work, the setup is operational.&lt;/p&gt;

&lt;p&gt;You can also check the message headers in Gmail to confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPF: pass&lt;/li&gt;
&lt;li&gt;DKIM: pass&lt;/li&gt;
&lt;li&gt;DMARC: pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the best real-world proof that your setup is correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common issues and fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Zoho still shows MX in red
&lt;/h3&gt;

&lt;p&gt;If mail is already working, the DNS is usually fine. Wait for propagation and check again later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. SPF is green but DKIM is still pending
&lt;/h3&gt;

&lt;p&gt;That usually means the DKIM TXT record is correct but Zoho has not rechecked it yet, or the record was added recently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Email is landing in spam
&lt;/h3&gt;

&lt;p&gt;Check all three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPF&lt;/li&gt;
&lt;li&gt;DKIM&lt;/li&gt;
&lt;li&gt;DMARC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also make sure your domain is not sending from an unauthorized service.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cloudflare says the record already exists
&lt;/h3&gt;

&lt;p&gt;Double-check for duplicates. You should have only one SPF TXT record and one DKIM TXT record for each selector.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optional improvements
&lt;/h2&gt;

&lt;p&gt;Once the core setup works, you can make it even better:&lt;/p&gt;

&lt;h3&gt;
  
  
  Create aliases
&lt;/h3&gt;

&lt;p&gt;A single inbox can receive mail for multiple addresses such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contact@yourdomain.com
hello@yourdomain.com
resume@yourdomain.com
jobs@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a signature
&lt;/h3&gt;

&lt;p&gt;A clean signature makes every email look more professional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shubham Singh
DevOps / DevSecOps Engineer

Website: https://yourdomain.com
LinkedIn: linkedin.com/in/your-link
GitHub: github.com/your-handle
Email: contact@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use your custom email everywhere
&lt;/h3&gt;

&lt;p&gt;Replace your Gmail address on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portfolio website&lt;/li&gt;
&lt;li&gt;Resume&lt;/li&gt;
&lt;li&gt;LinkedIn contact info&lt;/li&gt;
&lt;li&gt;Job applications&lt;/li&gt;
&lt;li&gt;GitHub profile&lt;/li&gt;
&lt;li&gt;Freelance profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps your brand consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this setup gives you
&lt;/h2&gt;

&lt;p&gt;This setup is not end-to-end encrypted email, but it does give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Professional custom email&lt;/li&gt;
&lt;li&gt;TLS encryption in transit&lt;/li&gt;
&lt;li&gt;SPF authentication&lt;/li&gt;
&lt;li&gt;DKIM signatures&lt;/li&gt;
&lt;li&gt;DMARC protection&lt;/li&gt;
&lt;li&gt;Better inbox deliverability&lt;/li&gt;
&lt;li&gt;A clean personal brand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a portfolio or professional website, this is exactly what you want.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final DNS record checklist
&lt;/h2&gt;

&lt;p&gt;Here is the full checklist in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;Zoho domain verification&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zoho-verification=zbXXXXXXXX.zmverify.zoho.in&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;Incoming mail delivery&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mx.zoho.in&lt;/code&gt;, &lt;code&gt;mx2.zoho.in&lt;/code&gt;, &lt;code&gt;mx3.zoho.in&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;SPF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=spf1 include:zoho.in ~all&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;DKIM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=DKIM1; k=rsa; p=...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;DMARC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v=DMARC1; p=quarantine; ...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With a domain, Cloudflare DNS, and Zoho Mail’s free plan, you can build a fully professional email address without paying for a large business email suite.&lt;/p&gt;

&lt;p&gt;For a personal portfolio, that is one of the best low-cost upgrades you can make.&lt;/p&gt;

&lt;p&gt;It improves your credibility, looks cleaner on your website, and gives your project a more polished finish.&lt;/p&gt;

&lt;p&gt;``&lt;/p&gt;

</description>
      <category>customemail</category>
      <category>zoho</category>
    </item>
  </channel>
</rss>
