Back to all posts
Fix hreflang Tags with Geo-Targeted Proxy Validation

Fix hreflang Tags with Geo-Targeted Proxy Validation

October 27, 2026

Why hreflang validation requires a location-aware check

hreflang tags tell search engines which language and country version of a page to serve. A tag like <link rel="alternate" hreflang="en-GB" href="https://example.com/uk/"> signals the UK English variant. The problem is that hreflang is interpreted server-side by crawlers, not rendered in the browser. If your origin server delivers different HTML based on the requester's IP, a check from your office in one country will not reveal what Googlebot sees from another.

Geo-targeted proxies close that gap. By routing requests through residential or ISP IPs in the target region, you can audit hreflang exactly as a local crawler would experience it.

How hreflang should work

A correct implementation follows three rules:

  1. Every localized page must link to all variants, including itself.
  2. The hreflang value must combine language and optional region using ISO 639-1 and ISO 3166-1 alpha-2 (for example, en-US, fr-FR, x-default).
  3. The href must be an absolute, canonical URL.

When any rule breaks, search engines may ignore the tags or serve the wrong version, which tanks traffic in that market.

Choosing proxies for hreflang audits

Use sticky residential proxies for each target country. Sticky sessions keep the same IP for the duration of the audit, which prevents a single page view from bouncing across regions and confusing geolocation checks. Rotating proxies are useful for high-volume crawls, but for validation you want consistency per region.

Recommended setup:

  • United States: residential IP, city-level targeting if available
  • United Kingdom: residential IP
  • Germany: residential IP
  • Canada: residential IP (English and French variants)
  • Australia: residential IP

Avoid datacenter IPs for this job. Many sites serve crawlers a challenge page or a generic version when they detect a datacenter ASN, which produces false negatives.

Building the hreflang validator

The script below fetches a page through a geo-targeted proxy, extracts hreflang links, and compares them against an expected map.

import httpx
from bs4 import BeautifulSoup

# Expected hreflang map: language-region -> absolute URL
EXPECTED = {
    'en-US': 'https://example.com/',
    'en-GB': 'https://example.com/uk/',
    'de-DE': 'https://example.com/de/',
    'x-default': 'https://example.com/',
}

PROXY_MAP = {
    'US': 'http://user:pass@GATEWAY_HOST:HTTP_PORT',
    'GB': 'http://user:pass@GATEWAY_HOST:HTTP_PORT',
    'DE': 'http://user:pass@GATEWAY_HOST:HTTP_PORT',
}

def fetch_via_proxy(country: str, url: str) -> str:
    proxy = PROXY_MAP[country]
    with httpx.Client(proxy=proxy, timeout=30, follow_redirects=True) as client:
        headers = {
            'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
            'Accept-Language': 'en-GB,en;q=0.9' if country == 'GB' else 'en-US,en;q=0.9',
        }
        response = client.get(url, headers=headers)
        response.raise_for_status()
        return response.text

def extract_hreflang(html: str) -> dict:
    soup = BeautifulSoup(html, 'html.parser')
    tags = {}
    for link in soup.find_all('link', rel='alternate'):
        hreflang = link.get('hreflang')
        href = link.get('href')
        if hreflang and href:
            tags[hreflang.lower()] = href
    return tags

def validate(country: str, url: str) -> list[str]:
    html = fetch_via_proxy(country, url)
    actual = extract_hreflang(html)
    errors = []
    for key, expected_url in EXPECTED.items():
        if key not in actual:
            errors.append(f'[{country}] Missing hreflang: {key}')
        elif actual[key] != expected_url:
            errors.append(
                f'[{country}] Mismatch for {key}: expected {expected_url}, got {actual[key]}'
            )
    return errors

if __name__ == '__main__':
    for country in ['US', 'GB', 'DE']:
        issues = validate(country, 'https://example.com/')
        if issues:
            print(f'\n{country} issues:')
            for issue in issues:
                print(' -', issue)
        else:
            print(f'{country}: hreflang OK')

What the script does

  • fetch_via_proxy routes the request through a country-specific proxy and sends a Googlebot-like user agent plus the local Accept-Language header.
  • extract_hreflang parses all rel="alternate" links and indexes them by hreflang.
  • validate compares the discovered tags with the expected map and returns human-readable errors.

Run it from your CI pipeline or a local scheduler. If a region returns a missing or wrong tag, investigate before the next search crawl.

Real-world example: an e-commerce site with three markets

A UK-based retailer operates example.com, example.com/uk/, and example.com/de/. From their London office, the homepage looks correct. When the team runs the validator through a German proxy, they discover two issues:

  1. The de-DE tag points to https://example.com/de without a trailing slash, while the canonical URL includes it. Search engines treat them as different pages.
  2. The x-default tag is missing entirely, so users outside the UK, Germany, and the US fall back to the wrong language.

Fixing the trailing slash and adding x-default recovered German organic traffic within two crawl cycles.

Common failure modes and fixes

Relative URLs in hreflang

Some CMSs output <link rel="alternate" hreflang="fr-FR" href="/fr/">. Google requires absolute URLs. Resolve relative paths against the page's canonical URL before comparing.

Missing self-reference

Each page must list itself. If example.com/uk/ omits its own en-GB tag, the cluster is invalid.

Conflicting canonical tags

If the canonical points to example.com/ while hreflang points to example.com/uk/, search engines may ignore the regional signal. Keep canonical and hreflang consistent.

IP-based content blocking

Some sites serve a consent wall or redirect to a local domain when they detect a foreign IP. If your proxy IP triggers a 302 to example.com/uk/ from a US node, the hreflang audit will fail. Use a sticky residential IP from the target region and check the final URL after redirects.

x-default misuse

x-default should point to the language/region picker or the generic international version, not to a specific country page.

Automating the check in CI

Add the script to your deployment pipeline so hreflang regressions are caught before they reach production. A minimal GitHub Actions step looks like this:

name: hreflang-validation
on: [push]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install dependencies
        run: pip install httpx beautifulsoup4
      - name: Run hreflang validator
        env:
          RO_PROXY_US: ${{ secrets.RO_PROXY_US }}
          RO_PROXY_GB: ${{ secrets.RO_PROXY_GB }}
          RO_PROXY_DE: ${{ secrets.RO_PROXY_DE }}
        run: python scripts/validate_hreflang.py

Inject proxy credentials as encrypted secrets, never hardcode them.

Pre-launch checklist

  • Every page includes self-referencing hreflang tags.
  • All href values are absolute and match canonical URLs.
  • x-default points to the international or selector page.
  • Validation passes from a proxy in each target country.
  • No 301/302 redirects alter the hreflang HTML before parsing.
  • Datacenter IPs are excluded; residential or ISP proxies are used.

Final thought

Hreflang is invisible to users but critical for search visibility. Because search engines evaluate it from their own crawling locations, you cannot validate it from a single vantage point. Geo-targeted proxies let you audit the signal where it matters, catch configuration drift early, and protect international traffic.