<?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>Forem: SimpleMeteo</title>
    <description>The latest articles on Forem by SimpleMeteo (@simplemeteo).</description>
    <link>https://forem.com/simplemeteo</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3902877%2F6f5c90d7-7e5b-45cd-8a7d-6b36d709d4c3.png</url>
      <title>Forem: SimpleMeteo</title>
      <link>https://forem.com/simplemeteo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/simplemeteo"/>
    <language>en</language>
    <item>
      <title>Why we self-hosted Open-Meteo: AI crawlers, rate limits, and 100 ms we didn't expect to win</title>
      <dc:creator>SimpleMeteo</dc:creator>
      <pubDate>Tue, 28 Apr 2026 17:35:05 +0000</pubDate>
      <link>https://forem.com/simplemeteo/why-we-self-hosted-open-meteo-ai-crawlers-rate-limits-and-100-ms-we-didnt-expect-to-win-cp8</link>
      <guid>https://forem.com/simplemeteo/why-we-self-hosted-open-meteo-ai-crawlers-rate-limits-and-100-ms-we-didnt-expect-to-win-cp8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR — We run &lt;a href="https://uvi.today" rel="noopener noreferrer"&gt;uvi.today&lt;/a&gt; (UV index), &lt;a href="https://pollen.today" rel="noopener noreferrer"&gt;pollen.today&lt;/a&gt; (pollen forecast) and &lt;a href="https://airindex.today" rel="noopener noreferrer"&gt;airindex.today&lt;/a&gt; (air quality). All three pull from Open-Meteo. AI crawlers pushed us past the free tier, the paid tier worked but still wasn't a fit for crawler-heavy traffic, so we ended up self-hosting Open-Meteo on a single VPS. Disk: ~50 GB. Latency: 90–100 ms faster per request. Cost: less than the paid plan.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a short write-up of how we got there, what the migration actually looked like, and a couple of things we'd flag for anyone thinking about doing the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The three sites are simple Next.js apps. Each city page renders server-side and calls Open-Meteo for two datasets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;forecast API&lt;/strong&gt; for temperature, weather code, humidity, etc.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;CAMS air quality API&lt;/strong&gt; for UV index, AQI components, and pollen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We cache responses in an LRU on the Node side (coord-keyed, 60 min TTL) and that's it. No queue, no warm cache jobs, no background workers.&lt;/p&gt;

&lt;p&gt;For the first months, the free public Open-Meteo API was perfect. The free tier is generous:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Per minute&lt;/th&gt;
&lt;th&gt;Per hour&lt;/th&gt;
&lt;th&gt;Per day&lt;/th&gt;
&lt;th&gt;Per month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;300,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: &lt;a href="https://open-meteo.com/en/pricing" rel="noopener noreferrer"&gt;open-meteo.com/en/pricing&lt;/a&gt;. The free tier is non-commercial; the Standard plan is what you upgrade to once you put ads on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the crawlers showed up
&lt;/h2&gt;

&lt;p&gt;Search Console traffic was modest. Logs were not. Once the sites started ranking on long-tail queries, every AI crawler on the planet decided that a city-by-locale URL grid was an irresistible buffet.&lt;/p&gt;

&lt;p&gt;In our access logs we kept seeing the same User-Agents on tight loops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GPTBot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClaudeBot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Bytespider&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Amazonbot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Meta-ExternalAgent&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The peak we measured before blocking them was around &lt;strong&gt;15,000 requests per hour from a single bot&lt;/strong&gt;. On a sister project that took longer to get blocking right, we saw bursts close to 200k/day. None of these bots respect any kind of "please slow down". They either get a 200, a 429, or a 403 — pick one.&lt;/p&gt;

&lt;p&gt;We picked 403, eventually. But before we did, the public Open-Meteo API started returning 429s during peaks, and our pages started erroring out for real users.&lt;/p&gt;

&lt;p&gt;The math is brutal: 100 cities × 9 locales = 900 cacheable URLs. With a 60 minute cache TTL that's 900 origin requests per hour worst case for our own users. Add a single misbehaving crawler that ignores cache headers and asks for &lt;code&gt;?lat=…&amp;amp;lon=…&lt;/code&gt; with random rounding, and the cache hit rate collapses. We were burning through 10,000 calls/day in a few hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  We tried the paid tier first
&lt;/h2&gt;

&lt;p&gt;The Standard plan removes the per-minute, per-hour and per-day caps and gives you 1,000,000 calls per month on a &lt;code&gt;customer-api.open-meteo.com&lt;/code&gt; host. Switching is one env var:&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;# .env&lt;/span&gt;
&lt;span class="nv"&gt;OPENMETEO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/uv-api.ts (excerpt)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;omHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENMETEO_HOST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;omHost&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENMETEO_API_KEY&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CAMS_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omHost&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;omHost&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/air-quality`&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://customer-air-quality-api.open-meteo.com/v1/air-quality&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://air-quality-api.open-meteo.com/v1/air-quality&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open-Meteo are upfront in their FAQ that monthly limits aren't being enforced yet — they're still building the usage portal — so in practice the Standard plan is "soft 1M/month, dedicated servers, commercial use OK". This solved the rate-limit problem immediately.&lt;/p&gt;

&lt;p&gt;It did not solve two other things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crawler load is wasteful spend.&lt;/strong&gt; Even if the limit isn't enforced, paying for traffic that produces no revenue (and no useful index entry on most platforms) is irritating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; Every page render fans out to two API hosts in another datacenter. We were measuring p50 around 180–220 ms per upstream call from our box. CAMS pollen + forecast = two of those, mostly serial.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Self-hosting Open-Meteo
&lt;/h2&gt;

&lt;p&gt;This is the part that surprised us: it is genuinely easy.&lt;/p&gt;

&lt;p&gt;Open-Meteo publishes a single Docker image (&lt;code&gt;ghcr.io/open-meteo/open-meteo&lt;/code&gt;) that does both jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;serve&lt;/code&gt; — runs the API on port 8080. Same query syntax as the public API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sync &amp;lt;model&amp;gt; &amp;lt;variables&amp;gt;&lt;/code&gt; — pulls the latest model run from the upstream provider (DWD, NOAA, ECMWF, MET Norway, …) and writes it to a shared volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You run one &lt;code&gt;serve&lt;/code&gt; and as many &lt;code&gt;sync&lt;/code&gt; workers as you have models you care about. Each &lt;code&gt;sync&lt;/code&gt; job re-runs on an interval (&lt;code&gt;--repeat-interval 5&lt;/code&gt; = every 5 minutes) and stores the last N days of past data (&lt;code&gt;--past-days 3&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For us, the relevant compose file looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;open-meteo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/open-meteo/open-meteo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;open-meteo-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/app/data&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;sync-dwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/open-meteo/open-meteo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;open-meteo-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/app/data&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sync&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dwd_icon,dwd_icon_eu,dwd_icon_d2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temperature_2m,relative_humidity_2m,weather_code,cloud_cover,precipitation,shortwave_radiation&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--past-days=3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--repeat-interval=5&lt;/span&gt;

  &lt;span class="na"&gt;sync-cams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/open-meteo/open-meteo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;open-meteo-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/app/data&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sync&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cams_global,cams_europe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;uv_index,uv_index_clear_sky,pm10,pm2_5,ozone,alder_pollen,birch_pollen,grass_pollen,ragweed_pollen&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--past-days=3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--repeat-interval=5&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;open-meteo-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We sync six model groups in total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DWD ICON&lt;/strong&gt; (11 km global, 7 km EU, 2 km Central EU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NCEP&lt;/strong&gt; (GFS 13 km global, HRRR 3 km CONUS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECMWF IFS&lt;/strong&gt; 25 km — long-range&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MET Norway / UKMO / BOM / CMC&lt;/strong&gt; for regional accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAMS&lt;/strong&gt; global + Europe — UV, AQI, pollen&lt;/li&gt;
&lt;li&gt;A one-off &lt;code&gt;copernicus_dem90&lt;/code&gt; sync for elevation data (~10 GB, runs once)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Application-side change is one line: set &lt;code&gt;OPENMETEO_HOST=http://open-meteo:8080&lt;/code&gt; and the existing client code routes there instead of the public API. No query rewriting needed — that's the nice part of Open-Meteo's design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the box actually looks like
&lt;/h2&gt;

&lt;p&gt;Real numbers from a single VPS (8 vCPU, 16 GB RAM, 150 GB disk) running everything — three sites, Caddy, an IP-geo service, monitoring, and the full Open-Meteo stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disk used by Open-Meteo data:&lt;/strong&gt; ~50 GB and stable. The DEM is the largest one-time cost (~10 GB). The rolling weather data stays bounded by &lt;code&gt;--past-days&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;open-meteo&lt;/code&gt; serve container at steady state:&lt;/strong&gt; ~1.1 GiB RAM, ~4 % of one core. Model files are mmapped, so the kernel page cache does most of the work — it's why &lt;code&gt;free -h&lt;/code&gt; shows ~13 GiB sitting in &lt;code&gt;buff/cache&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync workers:&lt;/strong&gt; burst CPU when a new model run lands (every 1–6 hours depending on model), idle the rest of the time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial sync:&lt;/strong&gt; 1–2 hours for the first run. This is the only painful step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a quieter footprint than we expected. Open-Meteo's storage format (&lt;a href="https://openmeteo.substack.com/" rel="noopener noreferrer"&gt;here's their write-up&lt;/a&gt;) is a custom layout designed for exactly this kind of mmap-friendly point lookups, and you can feel that in the metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency win
&lt;/h2&gt;

&lt;p&gt;We weren't optimising for this — we just wanted the rate limits gone — but it turned into the most visible result.&lt;/p&gt;

&lt;p&gt;Measured per-call upstream latency from our app container to Open-Meteo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public API (&lt;code&gt;api.open-meteo.com&lt;/code&gt;): ~100-110 ms&lt;/li&gt;
&lt;li&gt;Customer API (&lt;code&gt;customer-api.open-meteo.com&lt;/code&gt;): comparable, slightly more consistent&lt;/li&gt;
&lt;li&gt;Local container (&lt;code&gt;http://open-meteo:8080&lt;/code&gt;): ~10 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per page render that's &lt;strong&gt;roughly 90–100 ms shaved off&lt;/strong&gt;, twice (forecast + CAMS), most of it serial. For a server-rendered Next.js page that has to land HTML before the browser can paint, this is meaningful — we saw it directly in our TTFB numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we didn't migrate
&lt;/h2&gt;

&lt;p&gt;Not everything makes sense to host yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Geocoding&lt;/strong&gt; (&lt;code&gt;geocoding-api.open-meteo.com&lt;/code&gt;). It's a separate service with its own dataset; we kept it on the public API and put a 1-hour LRU cache in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical / climate / ensemble APIs.&lt;/strong&gt; We don't use them. If you do, note that the Standard plan also doesn't include them — that's a Professional-tier thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marine / flood APIs.&lt;/strong&gt; Same — out of scope for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The gotchas
&lt;/h2&gt;

&lt;p&gt;A few things to know before you copy the docker-compose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick variables deliberately.&lt;/strong&gt; Each &lt;code&gt;sync&lt;/code&gt; command takes an explicit list of variables. Adding a variable later means re-syncing — don't be too minimal at first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk growth is mostly the DEM.&lt;/strong&gt; The rolling weather data stays small &lt;em&gt;if&lt;/em&gt; &lt;code&gt;--past-days&lt;/code&gt; is small. Set this honestly — we use 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no built-in API key / rate limit on the local instance.&lt;/strong&gt; It binds to a private Docker network in our case; if you expose it to the internet, put a reverse proxy with auth or a rate limiter in front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawlers will still hit your app.&lt;/strong&gt; Self-hosting Open-Meteo doesn't solve the crawler problem — it just stops the crawler problem from cascading into a third-party rate-limit problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribution still applies.&lt;/strong&gt; Open-Meteo's data is CC BY 4.0; you keep crediting the underlying data sources (DWD, NOAA, ECMWF, CAMS, …) regardless of how you host it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;For our shape of traffic — small site, three domains sharing the same upstream, lots of automated traffic — yes, comfortably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capacity:&lt;/strong&gt; effectively unbounded for our scale. We can let crawlers through if we ever change our mind without watching a meter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; ~10 ms per upstream call, twice per render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; one VPS that we already had, instead of a per-domain subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational risk:&lt;/strong&gt; lower than expected. The image is one container, the syncs are independent, and a failed sync just means stale-but-still-served data for that model.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by the team behind &lt;a href="https://uvi.today" rel="noopener noreferrer"&gt;uvi.today&lt;/a&gt;, &lt;a href="https://pollen.today" rel="noopener noreferrer"&gt;pollen.today&lt;/a&gt;, &lt;a href="https://simplemeteo.com" rel="noopener noreferrer"&gt;SimpleMeteo&lt;/a&gt; and &lt;a href="https://airindex.today" rel="noopener noreferrer"&gt;airindex.today&lt;/a&gt;. We post the engineering side on X — &lt;a href="https://x.com/SimpleMeteo" rel="noopener noreferrer"&gt;@SimpleMeteo&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
