{"slug": "multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing", "title": "Multi-tenant SaaS for $30/mo: wildcard SSL + nginx vhost generator + Flask routing", "summary": "This article describes a cost-effective multi-tenant SaaS architecture that runs over 50 tenants on a single $30/month VPS using a wildcard SSL certificate, an nginx regex-based virtual host generator, and a Flask routing system that scopes data by partner slug. The setup eliminates the need for per-tenant configuration or Kubernetes by automatically provisioning new partners via a webhook, with the only write operation being a database row insertion. The author argues that many developers over-engineer with microservices and managed cloud services when a simple monolith with SQLite is sufficient for early-stage growth.", "body_md": "# Multi-tenant SaaS for $30/mo: the actual architecture\n\nEvery \"build a SaaS\" tutorial reaches for Kubernetes, managed Postgres, and three cloud services before the first user signs up. You don't need that. Here's a multi-tenant setup that runs 50+ tenants on subdomains, on one $30/mo VPS.\n\n## The requirement\n\nWhite-label partnership program. Each partner gets a storefront at `partner-{slug}.guardlabs.online`\n\nwith our catalog, their referral IDs baked in. New partner → automatic provisioning after payment webhook.\n\n## Phase 1 (0-50 tenants): wildcard SSL + nginx + Flask\n\n**Wildcard SSL via Let's Encrypt** — one cert covers `*.guardlabs.online`\n\n:\n\n```\ncertbot certonly --manual --preferred-challenges dns \\\n  -d \"*.guardlabs.online\" -d \"guardlabs.online\"\n```\n\n(DNS-01 challenge — add the TXT record your DNS provider, certbot validates.)\n\n**nginx server block** routing by `$host`\n\n:\n\n```\nserver {\n    listen 443 ssl;\n    server_name ~^partner-(?<partner_slug>.+)\\.guardlabs\\.online$;\n    ssl_certificate     /etc/letsencrypt/live/guardlabs.online/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/guardlabs.online/privkey.pem;\n\n    location / {\n        proxy_pass http://127.0.0.1:8090;\n        proxy_set_header Host $host;\n        proxy_set_header X-Partner-Slug $partner_slug;\n    }\n}\n```\n\n**Flask reads the header**, scopes everything to that partner:\n\n``` python\n@app.before_request\ndef load_partner():\n    slug = request.headers.get(\"X-Partner-Slug\")\n    if slug:\n        g.partner = db.execute(\n            \"SELECT * FROM partners WHERE slug=?\", (slug,)\n        ).fetchone()\n        if not g.partner:\n            abort(404)\n\n@app.route(\"/\")\ndef storefront():\n    products = get_catalog()  # global catalog\n    return render_template(\"store.html\",\n        products=products,\n        ref_id=g.partner[\"ref_id\"],  # injected into every buy link\n        partner_brand=g.partner[\"brand_name\"])\n```\n\n**Provisioning on webhook** — when Whop sends \"new subscription\":\n\n``` python\n@app.route(\"/webhook/whop\", methods=[\"POST\"])\ndef whop_webhook():\n    event = verify_and_parse(request)\n    if event[\"type\"] == \"subscription.created\":\n        slug = slugify(event[\"user\"][\"username\"])\n        # 1. DB row\n        db.execute(\"INSERT INTO partners (slug, ref_id, ...) VALUES (...)\")\n        # 2. nginx — no per-partner config needed! Regex server_name catches it.\n        # 3. Done. partner-{slug}.guardlabs.online works immediately.\n    return \"\", 200\n```\n\nThat's the trick: **the regex server_name means zero new nginx config per partner.** Wildcard SSL means zero new certs. The DB row is the only write.\n\n**Capacity:** one VPS, monolith Flask, SQLite. ~50 partners with 10 concurrent users each = 500 concurrent. A $30/mo Hetzner CCX handles that without breathing hard.\n\n## Phase 2 (50-200 tenants): per-tenant SQLite\n\nMove from shared DB to `/data/partners/{partner_id}/store.db`\n\n. Flask switches connection by `X-Partner-Slug`\n\n. ~16 hours of refactor. SQLite holds 10k+ rows per file fine; 200 partners × 10k = 2M rows total, no problem.\n\n## Phase 3 (200-500+): PostgreSQL schema-per-tenant\n\nNow you migrate. One Postgres, schema per partner. 40 hours. *Now* you might want Kubernetes. Not before.\n\n## The point\n\nI provisioned this whole thing in about 8 hours. Total infra budget for 12 months: under $5K. The \"you need microservices and managed everything\" advice is for companies with funding and a platform team. For a solo founder: nginx regex + wildcard SSL + a Flask `before_request`\n\nhook gets you to 50 paying tenants.\n\n## Try it\n\nThe partnership it powers: 7-day free trial, no card, $29/mo. [guardlabs.online/partner](https://guardlabs.online/partner/). And the bot the whole thing grew out of: Phantom paper-trader (384 trades, 57% win-rate, public).\n\n## Question\n\nWhat's your \"we over-engineered this\" story? When did you reach for Kubernetes before you needed it?\n\n*Code and full launch log in public. Following along.*\n\n### 📥 Free chapter — 20 no-budget growth tactics\n\nThis launch log runs on a playbook. If you want the actual tactics — Google-ecosystem hacks, trend-jacking, the HARO authority play — [grab two free sections of the Blueprint](https://guardlabs.online/free-pdf/?utm_source=blog&utm_medium=post&utm_campaign=buildinpublic). No PDF wall, no login: it opens in your browser. Real numbers, real code, no fluff.", "url": "https://wpnews.pro/news/multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing", "canonical_source": "https://dev.to/guardlabs_team/multi-tenant-saas-for-30mo-wildcard-ssl-nginx-vhost-generator-flask-routing-597k", "published_at": "2026-05-23 21:00:03+00:00", "updated_at": "2026-05-23 21:32:42.909556+00:00", "lang": "en", "topics": ["cloud-computing", "developer-tools", "startups", "enterprise-software"], "entities": ["Let's Encrypt", "nginx", "Flask", "guardlabs.online"], "alternates": {"html": "https://wpnews.pro/news/multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing", "markdown": "https://wpnews.pro/news/multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing.md", "text": "https://wpnews.pro/news/multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing.txt", "jsonld": "https://wpnews.pro/news/multi-tenant-saas-for-30-mo-wildcard-ssl-nginx-vhost-generator-flask-routing.jsonld"}}