robots.txt for Staging and Development Sites

How to prevent staging and development sites from getting indexed by Google. Covers robots.txt, noindex headers, password protection, and environment-specific configuration.

Staging sites get indexed by Google more often than you might expect. A developer shares a staging URL in a public Slack channel. A QA tester posts it in a bug report that ends up in a public issue tracker. A third-party tool crawls the subdomain. Google finds the link, follows it, and suddenly your half-finished redesign is showing up in search results next to your production site.

This is a real problem. Indexed staging sites create duplicate content issues, confuse users who land on broken pages, and can leak unreleased features or sensitive data. The good news: preventing it is straightforward if you use the right approach.

Why Staging Sites Get Indexed

Search engines find URLs through links. If any publicly accessible page links to your staging site -- even indirectly -- Google can discover it and start crawling. Common ways staging URLs leak:

  • DNS records are public. If staging.example.com has an A record, anyone (including bots) can find it through DNS enumeration.
  • Certificate Transparency logs. If you issue an SSL certificate for staging.example.com, the domain appears in public CT logs that crawlers monitor.
  • Third-party tools. Analytics scripts, error tracking services, and chat widgets on your staging site may report the URL back to services that expose it.
  • Backlinks from internal tools. Jira, Notion, GitHub, and other tools with public pages or indexed content can inadvertently link to staging URLs.
  • Crawlers following subdomains. Some crawlers systematically try common subdomains like staging., dev., test., and beta. on known domains.

Once a crawler finds the URL and the page returns a 200 status code, it can be indexed. The absence of a robots.txt file tells crawlers everything is fair game.

Option 1: Block With robots.txt

The simplest approach is to add a robots.txt file to your staging site that blocks all crawlers:

User-agent: *
Disallow: /

This tells every crawler to stay away from every path on the site. Place this file at the root of your staging domain so it is accessible at https://staging.example.com/robots.txt.

When This Works

This is effective for preventing crawling. Well-behaved crawlers like Googlebot and Bingbot will check robots.txt and respect the Disallow: / rule. They will not crawl any pages on the site.

When This Falls Short

There is an important distinction between crawling and indexing. robots.txt prevents crawling, but it does not prevent indexing. If another site links to your staging URL, Google may still add the URL to its index -- it just will not have any content to show. The search result will look something like:

staging.example.com No information is available for this page. Learn why

That is better than having your full staging content indexed, but the URL still appears in search results. For many teams, this is good enough. For others, complete removal from search results requires additional measures. For more on why this happens, see robots.txt Disallow Explained.

Option 2: Noindex HTTP Header

For complete deindexing, use the X-Robots-Tag HTTP header instead of (or in addition to) robots.txt. This header tells search engines not to index the page at all.

Configure your staging server to add this header to every response:

X-Robots-Tag: noindex

In Nginx:

server {
    listen 443 ssl;
    server_name staging.example.com;

    add_header X-Robots-Tag "noindex" always;

    # ... rest of your config
}

In Apache:

<VirtualHost *:443>
    ServerName staging.example.com
    Header set X-Robots-Tag "noindex"
</VirtualHost>

In Express.js (Node):

app.use((req, res, next) => {
  res.setHeader('X-Robots-Tag', 'noindex');
  next();
});

In Next.js (via next.config.js):

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          { key: 'X-Robots-Tag', value: 'noindex' },
        ],
      },
    ];
  },
};

Why This Works Better Than robots.txt Alone

The X-Robots-Tag: noindex header tells Google to drop the URL from search results entirely. Unlike robots.txt, which just blocks crawling, this actively instructs the search engine not to index the content.

There is a critical detail here: for Google to see the noindex header, it needs to be able to fetch the page. If you also have Disallow: / in robots.txt, Google cannot fetch the page and therefore cannot see the noindex header. The two directives cancel each other out.

If you want to use noindex, do not block the pages in robots.txt. Let Google crawl them, see the noindex header, and remove them from the index. For a deeper explanation of this interaction, see Noindex in robots.txt.

Do not combine Disallow and noindex for the same URLs

If robots.txt blocks a page with Disallow, crawlers cannot fetch it. If they cannot fetch it, they cannot see the noindex header. Choose one approach: either block crawling with robots.txt, or allow crawling but add noindex to prevent indexing. Using both together means the noindex is never seen.

Option 3: Password Protection

The most reliable way to keep staging sites out of search results is to prevent access entirely. If a crawler cannot reach the page, it cannot index it. Period.

HTTP Basic Authentication

Add basic authentication to your staging server. Every request requires a username and password:

# Nginx
server {
    listen 443 ssl;
    server_name staging.example.com;

    auth_basic "Staging";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
# Apache
<VirtualHost *:443>
    ServerName staging.example.com
    AuthType Basic
    AuthName "Staging"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</VirtualHost>

Crawlers receive a 401 Unauthorized response and move on. No content is accessible, so nothing gets indexed.

Platform-Level Password Protection

Many hosting platforms offer built-in password protection:

  • Vercel: Project Settings > Password Protection
  • Netlify: Site Settings > Access Control > Password Protection
  • Cloudflare Pages: Access Policies via Cloudflare Access
  • AWS: CloudFront with Lambda@Edge for authentication

These platform-level solutions are easy to enable and do not require server configuration changes.

IP Allowlisting

If your staging site only needs to be accessible from your office or VPN, restrict access by IP address:

# Nginx - allow only office and VPN IPs
server {
    listen 443 ssl;
    server_name staging.example.com;

    allow 203.0.113.0/24;  # Office IP range
    allow 198.51.100.50;   # VPN exit IP
    deny all;
}

Crawlers from Google and other search engines will get a 403 Forbidden response. This is the most secure approach but requires maintaining an IP allowlist.

Check how crawlers see your staging site

Test your staging robots.txt rules against real crawler user agents. Make sure your blocks are working before Google finds your dev site.

Test Your robots.txt

Managing robots.txt Across Environments

The trickiest part of using robots.txt for staging protection is making sure the right version ends up on the right environment. You need Disallow: / on staging but not on production. Here are common approaches.

Environment-Specific Static Files

Keep separate robots.txt files for each environment and deploy the correct one:

/config/robots.txt.production
/config/robots.txt.staging

Your deployment pipeline copies the appropriate file:

# In your deploy script
if [ "$ENVIRONMENT" = "production" ]; then
  cp config/robots.txt.production public/robots.txt
else
  cp config/robots.txt.staging public/robots.txt
fi

Dynamic robots.txt Generation

Serve robots.txt dynamically based on the environment. This is cleaner than managing multiple static files.

In Express.js:

app.get('/robots.txt', (req, res) => {
  res.type('text/plain');
  if (process.env.NODE_ENV === 'production') {
    res.send('User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml');
  } else {
    res.send('User-agent: *\nDisallow: /');
  }
});

In Next.js (app router):

// app/robots.ts
export default function robots() {
  if (process.env.VERCEL_ENV !== 'production') {
    return {
      rules: { userAgent: '*', disallow: '/' },
    };
  }
  return {
    rules: { userAgent: '*', allow: '/' },
    sitemap: 'https://example.com/sitemap.xml',
  };
}

In Django:

from django.http import HttpResponse
from django.conf import settings

def robots_txt(request):
    if settings.DEBUG or not settings.IS_PRODUCTION:
        content = "User-agent: *\nDisallow: /"
    else:
        content = "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml"
    return HttpResponse(content, content_type="text/plain")

CI/CD Pipeline Validation

Add a check to your deployment pipeline that verifies the correct robots.txt is in place after deployment:

# Post-deploy check for staging
ROBOTS=$(curl -s https://staging.example.com/robots.txt)
if echo "$ROBOTS" | grep -q "Disallow: /"; then
  echo "Staging robots.txt is correct - blocking all crawlers"
else
  echo "WARNING: Staging robots.txt is not blocking crawlers!"
  exit 1
fi

This catches the most dangerous mistake: accidentally deploying a production robots.txt to staging, or worse, deploying a staging robots.txt to production. For guidance on testing your rules, see How to Test robots.txt.

The Worst-Case Scenario: Staging robots.txt on Production

The nightmare scenario is deploying Disallow: / to your production site. This tells every search engine to stop crawling your entire site. Google will start dropping your pages from search results within days.

This happens more often than you would think. A deployment script copies the wrong file. An environment variable is not set correctly. Someone merges a staging config branch into main.

To protect against this:

  • Never use the same deployment pipeline for staging and production without environment checks
  • Add monitoring that alerts you if your production robots.txt contains Disallow: /
  • Use a separate domain for staging (like staging.example.com) rather than a path on the production domain
  • Review robots.txt changes in pull requests with the same scrutiny as code changes

If it does happen, fix the robots.txt immediately and request recrawling through Google Search Console. Google will start re-indexing your pages, but full recovery can take days to weeks depending on your site's size and crawl frequency.

Which Approach Should You Use?

The right approach depends on how sensitive your staging content is and how much effort you want to invest.

ApproachPrevents CrawlingPrevents IndexingPrevents AccessEffort
robots.txt Disallow: /YesPartiallyNoLow
X-Robots-Tag: noindexNoYesNoLow
Password protectionYesYesYesMedium
IP allowlistingYesYesYesMedium
robots.txt + noindex headerConflictConflictNoN/A

For most teams, password protection is the best option. It completely prevents access, which means crawling and indexing are non-issues. If password protection is not feasible (some testing workflows require unauthenticated access), use robots.txt with Disallow: / as the minimum viable solution, and add the X-Robots-Tag: noindex header without a Disallow rule if you need to ensure URLs do not appear in search results.

For a broader view of how robots.txt relates to other indexing controls, see robots.txt vs Meta Robots Tags.

References

[1] Google Search Central. "Block Search Indexing with noindex." https://developers.google.com/search/docs/crawling-indexing/block-indexing

[2] Google Search Central. "Introduction to robots.txt." https://developers.google.com/search/docs/crawling-indexing/robots/intro

[3] M. Koster, G. Illyes, H. Zeller, L. Harvey. "Robots Exclusion Protocol." RFC 9309, September 2022. https://datatracker.ietf.org/doc/html/rfc9309

Your staging site should be invisible to search engines. Pick the approach that fits your workflow -- robots.txt for simplicity, password protection for certainty -- and verify it is working before Google finds your dev environment.

Validate your staging robots.txt

Test your robots.txt rules to make sure staging is properly blocked. Check directives against real crawler user agents.

Test Your robots.txt