# How to Host Your Blog at yourdomain.com/blog With LotsBlog

Source: https://blog.lots.blog/host-blog-at-yourdomain-com-blog
Published: 2026-10-15T03:31:04.559884+00:00

Yes, LotsBlog can serve your blog at `yourdomain.com/blog`. You connect the blog with your domain and the path `/blog`, paste one rewrite rule into your site (LotsBlog gives you copy-ready rules for Cloudflare Workers, Vercel, Next.js, Netlify and Nginx), and run the check. Once the check passes, canonical URLs, the sitemap, RSS and llms.txt all use `yourdomain.com/blog`, and the blog's old address redirects there with a 301.

Your AI agent can do the LotsBlog side with two tools, `connect_domain` and `check_domain`, and the dashboard does the same job with a form and a Check again button. The one thing neither touches is your own site: you add the rewrite, so nothing changes on your domain without you.

Everything below comes from the LotsBlog code and API docs, and from two of our own product blogs that already run this way, [lotscrm.com/blog](https://lotscrm.com/blog) and [lots.link/blog](https://lots.link/blog), which I checked on 12 October 2026.

## Subdomain or /blog: which one?

LotsBlog supports both, so pick by how your site is run:

- **`blog.yourdomain.com` (a custom domain).** You add a CNAME record at your DNS provider and the blog runs on its own. Your marketing site and your blog never touch each other's config. This is the simpler setup.
- **`yourdomain.com/blog` (a path).** The blog shares a hostname with your marketing site, so readers and crawlers see one site. It costs you one rewrite in your site's config, and that rewrite has to stay in place for the blog to load.

Neither choice is a ranking promise. Both get the same canonical URLs, sitemap, RSS, structured data and llms.txt from LotsBlog. Teams usually pick `/blog` because they want one hostname in analytics, cookies and navigation, and a subdomain because they don't want blog changes anywhere near the main site's deploys.

## What you need

- **A LotsBlog blog you own.** Connecting an address is owner-only; editors and authors can't do it.
- **An active plan or credits.** New accounts get 10,000 free credits, valid 90 days, with no card. After that it's $9 per blog a month from credits, or plans from $24 a month for 3 blogs. Posts, page views and authors are unlimited. Details in [LotsBlog pricing explained](/lotsblog-pricing-explained).
- **Access to your site's config**: your Cloudflare zone, your Vercel or Netlify project, `next.config.js` or Nginx server block.
- **A valid path.** Lowercase letters, numbers, `-` and `_`, up to three segments, so `/blog`, `/resources/blog` and `/en/news/blog` all work. It can't start with `/api` or `/_next`, which belong to your app and to Next.js.

Use the hostname your site actually serves. If your marketing site lives at `www.yourdomain.com`, connect `www.yourdomain.com`, so the canonical URLs point at the address readers end up on.

## How it works

Your blog keeps running on its LotsBlog address, `https://YOUR-BLOG.lots.blog`. Your site forwards every request under `/blog` there, with `/blog` removed from the path, and adds a private proxy secret so LotsBlog knows the request came through your rewrite:

1. A reader opens `https://yourdomain.com/blog/my-post`.
2. Your rewrite forwards it to `https://YOUR-BLOG.lots.blog/my-post` with the secret, either as the `X-LotsBlog-Proxy-Secret` header or, in the Next.js rewrite, which can't add headers, in the path as `/_proxy/YOUR_PROXY_SECRET/my-post`.
3. LotsBlog checks the secret for that blog and renders the page with `https://yourdomain.com/blog` as its address. Links get the `/blog` prefix; scripts, styles and fonts load from the LotsBlog host.

The secret starts with `lbp_` and is tied to your blog, your hostname and your path. Change the domain or the path and you get a new secret. None of the rules contain it: each one reads it from a Worker secret or an environment variable named `LOTSBLOG_PROXY_SECRET` (Nginx: a small include file kept outside your repository), so the rule itself is safe to commit.

## Set it up from your AI agent

If your agent already has the LotsBlog MCP server (`https://api.lots.blog/mcp`), ask it in plain words:

> Use LotsBlog. Connect my "Acme" blog to acme.com at the path /blog. Show me the rewrite rule for Cloudflare Workers, then wait while I add it. When I say done, check it.

The agent calls `connect_domain` with three fields:

```json
{
  "blog_id": "YOUR_BLOG_ID",
  "domain": "acme.com",
  "path": "/blog"
}
```

`domain` is the hostname only, with no `https://` and no path. The reply holds everything you need: the proxy target (`https://YOUR-BLOG.lots.blog`), the private proxy secret, the name to store it under (`proxy.secret_name`, which is `LOTSBLOG_PROXY_SECRET`) with a `secret_note` on handling it, the header name, the check URL, the Sitemap line for your root robots.txt, and copy-ready rules for all five hosts. Each rule has a `secret_storage` line that says where the secret goes on that host. `connect_domain` runs a check straight away, in case your rewrite is already there; otherwise the status is "Waiting for your rewrite".

Add the rule on your site and deploy it, then tell the agent to check. It calls `check_domain` with just the `blog_id`. LotsBlog fetches `https://acme.com/blog/__lotsblog/subdirectory-check` through your rewrite and confirms it reached the right blog with the right secret. When it passes, the reply says the blog is live at `https://acme.com/blog/`.

Later, the agent can see where things stand with `get_blog`. Its `subdirectory` field shows the host, path, url, status (`pending` or `verified`), when it was verified and last checked, the last error and the next step. It never includes the secret. To stop serving the blog at the path, tell the agent to remove it: it calls `remove_subdirectory` with the `blog_id` and `confirm: true`, which only the blog's owner can do. Canonical URLs, the sitemap and RSS go back to your custom domain or `*.lots.blog` address, and you delete the rewrite from your site.

None of these tools changes your DNS or your site. If you prefer code to chat, the same operations are REST endpoints: `POST /api/v1/lotsblog/blogs/:blog_id/domain` with `domain` and `path`, `POST /api/v1/lotsblog/blogs/:blog_id/domain/check`, and `DELETE /api/v1/lotsblog/blogs/:blog_id/domain/subdirectory?confirm=true` to remove it, documented at [api.lots.blog/docs.md](https://api.lots.blog/docs.md). New to the MCP server? Start with [How to Publish Blog Posts From Claude Code](/publish-blog-posts-from-claude-code).

## Set it up from the dashboard

1. Open your blog in the [LotsBlog dashboard](https://lots.blog), go to **Settings**, then **Custom Domain**.
2. Under **Host at a path on your site**, type your site (`acme.com`) and the path (`/blog`), then press **Connect**.
3. The card shows the address with a **Waiting for your rewrite** badge, the proxy secret with a copy button and a note on where to store it, the Sitemap line for your robots.txt, and a tab for each host: Cloudflare Workers, Vercel, Next.js, Netlify and Nginx. Each tab says where the rule goes and has its own copy button.
4. Add the rule to your site and deploy it.
5. Press **Check again**. When the rewrite works, the badge turns to **Live**.

**Remove** on the same card disconnects the path. Readers go back to the old address, and you can delete the rewrite from your site.

## The rewrite rules, host by host

These are the rules LotsBlog generates, with placeholders: `acme.com` for your site and `YOUR-BLOG` for your blog's LotsBlog subdomain. Copy the real ones from your agent's reply or the dashboard, where every value is already filled in. The secret is never in them.

**Keep the proxy secret private.** Treat it like a password: store it only where the rule for your host reads it, never in code or a committed file. Anyone holding it can serve your blog through their own site, though LotsBlog also checks the forwarded hostname when the proxy sends one.

### Cloudflare Workers

Create a Worker, add the routes `acme.com/blog` and `acme.com/blog/*` in your `acme.com` zone, and store the proxy secret as the Worker secret `LOTSBLOG_PROXY_SECRET`: run `npx wrangler secret put LOTSBLOG_PROXY_SECRET` and paste it, or add it under the Worker's **Settings**, **Variables and Secrets** with type Secret. The Worker forwards the request with the secret as a header:

```js
// The proxy secret is the Worker secret LOTSBLOG_PROXY_SECRET, never in this file.
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const rest = url.pathname.slice("/blog".length) || "/";
    const upstream = new URL(rest + url.search, "https://YOUR-BLOG.lots.blog");
    const headers = new Headers(request.headers);
    headers.delete("host");
    headers.set("X-LotsBlog-Proxy-Secret", env.LOTSBLOG_PROXY_SECRET);
    headers.set("X-Forwarded-Host", url.hostname);
    const hasBody = request.method !== "GET" && request.method !== "HEAD";
    return fetch(upstream, { method: request.method, headers, body: hasBody ? await request.arrayBuffer() : undefined, redirect: "manual" });
  },
};
```

This is the setup lotscrm.com and lots.link use: both sites already run on Cloudflare Workers, so the blog proxy is a few lines at the top of the existing Worker, with the secret stored as a Worker secret.

### Vercel (Routing Middleware, any framework)

Save this as `middleware.js` at the root of the project that serves your main site. Add the environment variable `LOTSBLOG_PROXY_SECRET` with the proxy secret under **Project Settings**, **Environment Variables** for Production, then redeploy. For a Next.js app, use the Next.js rule below instead.

```js
// middleware.js: the proxy secret is the environment variable LOTSBLOG_PROXY_SECRET, never in this file.
export const config = { matcher: ["/blog", "/blog/:path*"] };

export default async function middleware(request) {
  const url = new URL(request.url);
  const rest = url.pathname.slice("/blog".length) || "/";
  const upstream = new URL(rest + url.search, "https://YOUR-BLOG.lots.blog");
  const headers = new Headers(request.headers);
  headers.delete("host");
  headers.set("X-LotsBlog-Proxy-Secret", process.env.LOTSBLOG_PROXY_SECRET);
  headers.set("X-Forwarded-Host", url.hostname);
  const hasBody = request.method !== "GET" && request.method !== "HEAD";
  return fetch(upstream, { method: request.method, headers, body: hasBody ? await request.arrayBuffer() : undefined, redirect: "manual" });
}
```

### Next.js (next.config.js rewrites)

Add this to `next.config.js` (or `.ts`) of your main site. Set the environment variable `LOTSBLOG_PROXY_SECRET` to the proxy secret where your site builds and runs (on Vercel: **Project Settings**, **Environment Variables**), not in a committed `.env` file, then redeploy, because rewrites are read at build time. It works on Vercel and on self-hosted Next.js:

```js
// The proxy secret is the environment variable LOTSBLOG_PROXY_SECRET, never in this file.
async rewrites() {
  const target = "https://YOUR-BLOG.lots.blog" + "/_proxy/" + process.env.LOTSBLOG_PROXY_SECRET;
  return {
    beforeFiles: [
      { source: "/blog", destination: target + "/" },
      { source: "/blog/:path*", destination: target + "/:path*" },
    ],
  };
},
```

`beforeFiles` makes the rewrite win over any `/blog` page in your own app.

### Netlify (Edge Function)

Save this as `netlify/edge-functions/lotsblog.js` in your main site's repository. Add the environment variable `LOTSBLOG_PROXY_SECRET` with the proxy secret under **Site configuration**, **Environment variables** (scope: Functions), then redeploy:

```js
// The proxy secret is the environment variable LOTSBLOG_PROXY_SECRET, never in this file.
export const config = { path: ["/blog", "/blog/*"] };

export default async function lotsblog(request) {
  const url = new URL(request.url);
  const rest = url.pathname.slice("/blog".length) || "/";
  const upstream = new URL(rest + url.search, "https://YOUR-BLOG.lots.blog");
  const headers = new Headers(request.headers);
  headers.delete("host");
  headers.set("X-LotsBlog-Proxy-Secret", Netlify.env.get("LOTSBLOG_PROXY_SECRET"));
  headers.set("X-Forwarded-Host", url.hostname);
  const hasBody = request.method !== "GET" && request.method !== "HEAD";
  return fetch(upstream, { method: request.method, headers, body: hasBody ? await request.arrayBuffer() : undefined, redirect: "manual" });
}
```

### Nginx

Add this inside the `server` block for `acme.com`:

```nginx
location = /blog { return 301 /blog/; }
location /blog/ {
    proxy_pass https://YOUR-BLOG.lots.blog/;
    proxy_ssl_server_name on;
    proxy_set_header Host YOUR-BLOG.lots.blog;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    # The X-LotsBlog-Proxy-Secret header line lives in this file, never here.
    include /etc/nginx/lotsblog-proxy-secret.conf;
    proxy_redirect off;
}
```

Then create `/etc/nginx/lotsblog-proxy-secret.conf`, outside any repository and readable only by root and Nginx, with one line: `proxy_set_header X-LotsBlog-Proxy-Secret "YOUR_PROXY_SECRET";` using your real secret. Run `nginx -t` and reload Nginx.

### Another host?

Any host that can reverse proxy works. Forward `acme.com/blog/*` to `https://YOUR-BLOG.lots.blog` with `/blog` removed from the path, and send the secret in the `X-LotsBlog-Proxy-Secret` header, or use `https://YOUR-BLOG.lots.blog/_proxy/YOUR_PROXY_SECRET` as the target if the host can't add headers.

## What changes once it's live

When the check passes, `yourdomain.com/blog` becomes the blog's address everywhere LotsBlog prints one. Here's what I saw on lotscrm.com/blog on 12 October 2026:

| What | Where LotsBlog points it | Seen on lotscrm.com/blog |
|---|---|---|
| Canonical URL | `yourdomain.com/blog/...` | `<link rel="canonical" href="https://lotscrm.com/blog/"/>` |
| Sitemap | `yourdomain.com/blog/sitemap.xml`, with URLs under `/blog` | `<loc>https://lotscrm.com/blog/</loc>` |
| RSS | `yourdomain.com/blog/rss.xml` | Feed self link `https://lotscrm.com/blog/rss.xml` |
| llms.txt | `yourdomain.com/blog/llms.txt` | "Public articles from https://lotscrm.com/blog", with RSS and sitemap links under `/blog` |
| Markdown copies | Under `/blog`, like the articles | |
| Old address | 301 to `/blog` | `lotscrm.lots.blog/` 301s to `https://lotscrm.com/blog/`, and `/rss.xml` to `/blog/rss.xml` |

lots.link/blog showed the same pattern: canonical `https://lots.link/blog/`, sitemap and llms.txt under `/blog`, and `lotslink.lots.blog` redirecting to `https://lots.link/blog/`.

### The 301 from your old address

Your `*.lots.blog` address, and your custom domain if you had one, now send readers to the same page under `/blog` with a permanent 301, keeping the path and query. Old links and old search results land on the new address. Your dashboard, sign-in and draft previews stay on the LotsBlog host, so nothing in your workflow moves.

A blog that went live stays live if a later check fails, for example during a deploy on your site. The address doesn't flip back and forth; the check reports the error so you can fix the rewrite.

### robots.txt: add lines to your root file

Search engines read robots.txt only at the root of a domain, `https://yourdomain.com/robots.txt`, never at `https://yourdomain.com/blog/robots.txt`. So the blog's crawl rules have to go into your site's own root file. At minimum, add the Sitemap line:

```text
Sitemap: https://yourdomain.com/blog/sitemap.xml
```

LotsBlog prepares the rest for you. The blog's rules are written for its own paths, and copied as they are, `Disallow: /api/` would block your app's `/api/`, not the blog's. For a blog at a path, `get_blog` returns `robots_txt.root_lines`: the same rules with every path starting with `/blog`, plus the Sitemap line. On the LotsCRM blog, `get_blog` returned these through the MCP today:

```text
# Blog at https://lotscrm.com/blog
User-agent: *
Allow: /blog/
Disallow: /blog/api/
Disallow: /blog/dashboard/
Disallow: /blog/auth/
Disallow: /blog/setup/

# Add sitemap link
Sitemap: https://lotscrm.com/blog/sitemap.xml
```

lotscrm.com's root robots.txt carries the `/blog/` Disallow lines and the blog's Sitemap line next to its own rules. The same prefixed lines are also in the dashboard under **Settings**, **General**, **Robots.txt Configuration**, and `https://yourdomain.com/blog/robots.txt` serves them under a comment explaining that crawlers don't read that file. To change the blog's own rules, your agent uses `update_blog` with `robots_txt`; the root lines follow. More on which AI crawlers to allow in [How to Let AI Crawlers Read Your Blog](/let-ai-crawlers-read-your-blog).

## Troubleshooting: what check_domain tells you

`check_domain` and the dashboard's Check again return a plain message. These are the messages in the LotsBlog code and what to do about each.

| Message | What it means | Fix |
|---|---|---|
| "...did not reach LotsBlog (HTTP 404). Add the rewrite for /blog on acme.com, deploy it, then check again." | The check URL answered, but not from LotsBlog. The rule isn't deployed, or your app answered `/blog` first | Deploy the rule. On Next.js keep it in `beforeFiles`. On Cloudflare, add both routes, `/blog` and `/blog/*` |
| "...reached a different LotsBlog blog. Point the rewrite at this blog's address." | The rule forwards to another blog's `*.lots.blog` host | Copy the target from this blog's setup again |
| "The rewrite reached LotsBlog without the proxy secret. Add the X-LotsBlog-Proxy-Secret header or use the /_proxy/<secret> target." | The rewrite works but the secret is missing | Check that `LOTSBLOG_PROXY_SECRET` is set where your rule reads it (Worker secret, Vercel or Netlify environment variable, Next.js environment variable at build time, or the Nginx include file), then redeploy |
| "The proxy secret or forwarded host does not match. Copy the secret again; it changes when the domain or path changes." | Wrong or old secret, or `X-Forwarded-Host` isn't the domain you connected | Copy the current secret. Connect the hostname your site really serves (`www.` and the bare domain count as the same) |
| "Could not reach https://acme.com/blog/__lotsblog/subdirectory-check: ..." | No answer at all: DNS, TLS or a timeout | Open the URL yourself and fix what your site returns |
| "Still live at https://acme.com/blog/, but the latest check failed: ..." | The address stays live, but the rewrite broke | Fix the rewrite; readers who reach the blog through it are affected |

You can run the same test yourself. Open `https://yourdomain.com/blog/__lotsblog/subdirectory-check`. Through a working rewrite it returns JSON like `{"ok":true,"blog_id":"...","proxied":true,"verified":true}`. `proxied: false` means the secret didn't arrive; `ok: false` with `proxied: true` means it arrived but doesn't match. A rewrite that sends a wrong secret gets a plain 403 from LotsBlog, never a redirect loop.

`connect_domain` can also refuse up front:

- **"domain must be your own site hostname..."**: send the hostname only, such as `acme.com`, not a URL and not a `lots.blog` address.
- **"path must be a path on your site such as /blog..."**: the path broke the rules in "What you need".
- **"This blog is already live at ... Remove it in the dashboard before connecting another subdirectory."**: one address at a time; remove the live one first, in the dashboard or with `remove_subdirectory`.
- **"This domain and path are already connected to another blog"** or **"This domain is connected to another LotsBlog blog"**: another blog holds that address.
- **"A plan or enough credits is required. Open https://lots.blog/billing to choose."**: the blog needs an active plan or credits.

## Start from your agent

Give your agent this and let it set itself up:

> Read https://lots.blog/skill.md and help me connect LotsBlog for you.

Then ask it to connect your blog at `/blog`. Weighing hosted blog platforms first? See [the best blogging platforms for SaaS startups with a custom domain](/best-blogging-platforms-for-saas-startups), and [LotsBlog pricing](https://lots.blog/pricing).

## Common questions

### Can LotsBlog host my blog at yourdomain.com/blog?

Yes. Connect the blog with your domain and a path such as `/blog`, add one rewrite rule to your site, and run the check. LotsBlog gives you copy-ready rules for Cloudflare Workers, Vercel, Next.js, Netlify and Nginx, and any host that can reverse proxy works.

### Do I need to change DNS records?

No. A blog at a path runs through a rewrite on your existing site, so your DNS stays as it is. DNS records (a CNAME) are only for a custom domain such as `blog.yourdomain.com`.

### Can my AI agent set it up?

Your agent does the LotsBlog side: `connect_domain` with your domain and path returns the rules and the secret, and `check_domain` verifies the rewrite. You add the rule to your site. LotsBlog never changes your DNS or your site.

### Is it safe to commit the proxy secret?

You never need to. None of the rules contain the secret: Cloudflare Workers, Vercel, Next.js and Netlify read it from a Worker secret or environment variable named `LOTSBLOG_PROXY_SECRET`, and Nginx from an include file kept outside your repository. Commit the rule, and keep the secret itself out of code and committed files.

### What happens to my old blog address?

Once the check passes, your `*.lots.blog` address and any custom domain redirect with a 301 to the same page under `/blog`. Canonical URLs, the sitemap, RSS and llms.txt switch to the new address.

### Why doesn't my /blog/robots.txt count?

Search engines read robots.txt only at the root of your domain. Add the blog's Sitemap line, and the prefixed rules from `get_blog` `robots_txt.root_lines` or the dashboard, to `yourdomain.com/robots.txt`.

### Can I use a path other than /blog?

Yes: lowercase letters, numbers, `-` and `_`, up to three segments, such as `/resources/blog`. It can't start with `/api` or `/_next`.

### Who can connect a path, and does it cost extra?

Only the blog's owner. It needs an active plan or credits, and there's no separate charge for the path: it's part of the blog, at $9 per blog a month or a plan from $24 for 3 blogs.

*LotsBlog details checked on 12 October 2026 (setup rules, `get_blog` and `remove_subdirectory` updated 13 October 2026) from [api.lots.blog/docs.md](https://api.lots.blog/docs.md) ("Blogs served at a path on your site"), the LotsBlog dashboard and engine code, `get_blog` through the LotsBlog MCP server, and the live blogs at [lotscrm.com/blog](https://lotscrm.com/blog) and [lots.link/blog](https://lots.link/blog). Prices from [lots.blog/pricing](https://lots.blog/pricing), read the same day.*
