diff --git a/blume.config.ts b/blume.config.ts index 1329d7c5..622b4149 100644 --- a/blume.config.ts +++ b/blume.config.ts @@ -167,6 +167,12 @@ export default defineConfig({ }, agents: { llmsTxt: { details: llmsDetails }, + // The WP Cloud footer, written out as Markdown for .md pages, llms-full.txt + // and MCP: without this the component ships as raw JSX. + markdownComponents: { + WpCloudDocs: ({ props }) => + `Spacefast is built on Automattic's [WP Cloud](https://wp.cloud/). Using the [Partner API](${deploymentBase}/platforms/partner-api)? See [${String(props.title)}](${String(props.href)}) on WP Cloud Docs.`, + }, // The Website publishes Spacefast's agent skill at the site root; a second, // generated Docs skill would compete with it. skillMd: false, diff --git a/bun.lock b/bun.lock index 46d832f3..f21c9a21 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "docs", "dependencies": { "blume": "2.2.6", - "zod": "4.6.0", + "zod": "4.6.5", }, "devDependencies": { "@spacefast/routing": "0.0.24", @@ -1665,7 +1665,7 @@ "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - "zod": ["zod@4.6.0", "", {}, "sha512-iIvwyDnebKYpww2ta0DjaNOL8RnVLmPMhgWyOzlW9y0EIIxv5gl+H6Y0ONpt83HqjGqkk78uWp6xWtpxzZdPbw=="], + "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], @@ -1705,8 +1705,6 @@ "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], - "@modelcontextprotocol/sdk/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], - "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@rollup/pluginutils/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], @@ -1759,12 +1757,8 @@ "astro/vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="], - "astro/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], - "blume/@types/node": ["@types/node@22.20.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA=="], - "blume/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], - "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], diff --git a/components.ts b/components.ts index b4799473..5b74a559 100644 --- a/components.ts +++ b/components.ts @@ -3,7 +3,9 @@ import { defineComponents } from "blume"; import Breadcrumbs from "./components/Breadcrumbs.astro"; import Pagination from "./components/Pagination.astro"; import Sidebar from "./components/Sidebar.astro"; +import WpCloudDocs from "./components/WpCloudDocs.astro"; export default defineComponents({ layout: { Breadcrumbs, Pagination, Sidebar }, + mdx: { WpCloudDocs }, }); diff --git a/components/WpCloudDocs.astro b/components/WpCloudDocs.astro new file mode 100644 index 00000000..231e9d75 --- /dev/null +++ b/components/WpCloudDocs.astro @@ -0,0 +1,16 @@ +--- +import { contentHref } from "blume/components/content/base-href.ts"; + +interface Props { + title: string; + href: string; +} + +const { title, href } = Astro.props; +--- + + diff --git a/content/(dynamic)/logs.mdx b/content/(dynamic)/logs.mdx index 8740ee54..b10482b9 100644 --- a/content/(dynamic)/logs.mdx +++ b/content/(dynamic)/logs.mdx @@ -7,15 +7,16 @@ sidebar: Use `sf logs` for requests, `sf logs runtime` for your code's output, and `sf builds logs` for build failures. -## Three streams +## Four streams | Stream | What it holds | How to read it | | --- | --- | --- | -| Access | Every request the edge served | `sf logs`, dashboard **Logs** with source **Access** | +| Access | Requests that reached the origin | `sf logs`, dashboard **Logs** with source **Access** | +| Edge | Requests handled by the edge, including cache hits and challenges | [`sf logs edge`](/infrastructure/edge-request-logs), dashboard **Logs** with source **Edge Logs** | | Runtime | Lines your own code wrote | `sf logs runtime`, dashboard **Logs** with source **App** | | Build | A build runner's output | `sf builds logs ` | -Access and runtime are per Space. Build logs belong to one build. +Access, edge, and runtime are per Space. Build logs belong to one build. ## Read them @@ -27,7 +28,7 @@ sf logs https://example.com access sf logs runtime --follow ``` -The first positional is a target, which can be a Space id, slug, live URL, or custom domain. The second is the kind. The command shifts a bare kind out of the first slot, so `sf logs runtime` and `sf logs docs runtime` both work. The kind defaults to `access`, and anything other than `access` or `runtime` is refused. +The first positional is a target, which can be a Space id, slug, live URL, or custom domain. The second is the kind. The command shifts a bare kind out of the first slot, so `sf logs runtime` and `sf logs docs runtime` both work. The kind defaults to `access`. Use `edge` for requests handled by the edge, including cache hits and security challenges. See [Edge request logs](/infrastructure/edge-request-logs) for its filters and query limits. | Flag | Default | What it does | | --- | --- | --- | @@ -77,7 +78,7 @@ A record is written on the origin, shipped off the box, and indexed before it ca `--request-id` and `--handler` are applied over pages already fetched, because the provider indexes neither. That makes them precise but not free, so give a `--limit` that covers the window you care about. -The `since` and `until` window exists on the API query, but the CLI exposes no flags for it. +Use `--since` and `--until` for an ISO 8601 time window. Edge logs have their own [window and retention limits](/infrastructure/edge-request-logs#filters-and-time-windows). ## Retention @@ -99,5 +100,6 @@ sf builds logs bld_123 --follow | Operation | Route | | --- | --- | | [`listSpaceAccessLogs`](/api/reference/activity/listspaceaccesslogs) | `GET /v1/spaces/{spaceId}/logs/access` | +| [`listSpaceEdgeLogs`](/api/reference/activity/listspaceedgelogs) | `GET /v1/spaces/{spaceId}/logs/edge` | | [`listSpaceRuntimeLogs`](/api/reference/activity/listspaceruntimelogs) | `GET /v1/spaces/{spaceId}/logs/runtime` | | [`listBuildLogs`](/api/reference/builds/listbuildlogs) | `GET /v1/builds/{buildId}/logs` | diff --git a/content/(serve)/domains.mdx b/content/(serve)/domains.mdx index eb42b0c5..f6875d29 100644 --- a/content/(serve)/domains.mdx +++ b/content/(serve)/domains.mdx @@ -86,13 +86,15 @@ Every observed `A` record has to point at Spacefast. A leftover record from your ### When a TXT record shows up -Most domains need no TXT record. One appears only when the hostname is already in use elsewhere and you have to prove it is yours: +Most domains need no TXT record. One appears only when the hostname is already in use elsewhere and you have to prove it is yours (see [Domain verification records](/infrastructure/domain-verification-records) for how they interact with older proofs): - **Another host still holds it.** The record is named for the hostname, its value starts with `atomic-domain-`, and the domain waits until it is published. Remove any CNAME at that name first; a TXT record cannot sit beside one. - **Another Space serves it.** Adding it here creates a claim instead of failing, and returns a `_spacefast-claim.` TXT record. If you can also manage the Space that holds it, verify with move and the domain moves without the record. Otherwise publish the record at your DNS host and verify: once Spacefast sees it, the domain moves to this Space and the old Space stops serving it. Either way, a claim expires 24 hours after it was created; add the domain again for a new one. Keep exactly one `atomic-domain-` record per hostname. Two of them, even when one is correct, make the claim fail. +Certificates are provisioned and renewed automatically once the hostname can be validated. See [TLS certificates](/infrastructure/tls-certificates) for issuance checks and retries, and [IP ranges](/infrastructure/ip-ranges) for inbound and outbound address behavior. + ## Apex and www travel together If the hostname you add is the registrable apex or its `www` twin, both hostnames get set up. `example.com` pulls in `www.example.com`, and `www.example.com` pulls in `example.com`. Any other subdomain, like `app.example.com`, is connected on its own. @@ -209,7 +211,7 @@ www.example.com purpose=www dns=not_configured txt=missing ssl=missing | `verification_required` | The hostname needs proof of DNS control to keep serving. | Add the verification TXT record. | | `domain_ownership_required` | Another Space serves this hostname. | Publish the claim's TXT record, then verify to move it here. | | `ssl_pending` | Certificate issuance is in progress. | Wait. | -| `ssl_renewal_blocked` | A DNS record, usually CAA, is blocking issuance. | Fix the record, then retry SSL. | +| `ssl_renewal_blocked` | A DNS record, usually CAA, is blocking issuance. | Fix the record, then retry HTTPS. | | `provider_pending` | The edge has not observed the hostname yet. | Wait. | | `provider_rejected` | The hostname claim was rejected. | Contact support. | | `provider_error` | Attaching or detaching the hostname failed. | Contact support. | diff --git a/content/(serve)/routing.mdx b/content/(serve)/routing.mdx index a1fcc871..1c428a1e 100644 --- a/content/(serve)/routing.mdx +++ b/content/(serve)/routing.mdx @@ -59,6 +59,8 @@ Two more tokens can appear on a line. Query matchers sit between the source and ## `_headers` +For a security policy example and browser checks, see [HTTP and security headers](/infrastructure/http-security-headers). + An unindented line starts a matcher block. Indented lines under it are operations. `!Name` removes a header. ```text _headers diff --git a/content/cli/storage.mdx b/content/cli/storage.mdx index fd201d29..bb4d82f7 100644 --- a/content/cli/storage.mdx +++ b/content/cli/storage.mdx @@ -5,7 +5,7 @@ sidebar: order: 23 --- -List your app's stored objects and read access or runtime logs within your plan's retention window. Runtime lines take time to appear, and static Spaces have no runtime logs. +List your app's stored objects and read access, edge, or runtime logs within your plan's retention window. Runtime lines take time to appear, and static Spaces have no runtime logs. Every command here takes the global flags (`--api-url`, `--profile`, `--token`, `-y`/`--yes`, `--json`) and the space selection flags (`--space`, `-o`/`--team`, `--claim-token`). See [the CLI overview](/cli). @@ -84,7 +84,7 @@ With `--json`, `data` carries `deleted`. Read a Space's logs. -`access` is every request the edge served. `runtime` is what your code logged, with the request id and handler for each line. A static Space has no runtime lines. +`access` covers requests that reached your Space's origin. `edge` covers requests handled at the edge, cache hits included. Each edge query reads one hostname (the primary unless you pass `--hostname`), and a busy window can be truncated; see [Edge request logs](/infrastructure/edge-request-logs) for its filters and limits. With `runtime` you get what your code logged, with the request id and handler for each line. A static Space has no runtime lines. A line you just triggered takes a while to appear — it's written on the origin, shipped, and indexed before it can be read back. **An empty page means not yet, not broken.** Keep paging while the cursor is there: a page can come back empty while there's still more to read. @@ -98,7 +98,7 @@ sf logs [target] [kind] [--space ] [--limit ] [-f] [--cursor ] | Argument | What it is | | --- | --- | | `[target]` | Space ID, slug, live URL, or domain. Defaults to the linked Space | -| `[kind]` | `access` or `runtime`. Defaults to `access` | +| `[kind]` | `access`, `edge`, or `runtime`. Defaults to `access` | A bare kind in the first slot works: `sf logs runtime` is the same as `sf logs --space runtime`. @@ -136,5 +136,5 @@ Log history is a plan limit. A request that reaches past your retention window i | Code | Exit | When | | --- | --- | --- | -| `validation_error` | 2 | `kind` is not `access` or `runtime` | +| `validation_error` | 2 | `kind` is not `access`, `edge`, or `runtime` | | `validation_error` | 2 | `--request-id` or `--handler` passed with the access log | diff --git a/content/infrastructure/automated-failover.mdx b/content/infrastructure/automated-failover.mdx new file mode 100644 index 00000000..2e70e769 --- /dev/null +++ b/content/infrastructure/automated-failover.mdx @@ -0,0 +1,26 @@ +--- +title: Automated failover +description: How regional replicas keep Spacefast sites available when an origin fails. +--- + +Spacefast sites run on infra with primary and secondary origin servers in different geographic data centers. WP Cloud continuously replicates site data between them and can move traffic when the primary server or its data center becomes unavailable. + +## What happens during failover + +WP Cloud monitors origin health and performance. When a failure requires a switch, it redirects traffic to the secondary origin. Once the primary is restored, traffic returns to it automatically. + +You do not need to republish your Space or change its DNS records. The public address reaches the [edge network](/infrastructure/origin-edge-servers), which routes requests to the active origin. + +The active origin location and outbound IP address can change during a switch. Do not use either as a permanent identifier for your Space. See [IP ranges](/infrastructure/ip-ranges) before connecting an external service that requires an IP allowlist. + +## Cached responses during an origin error + +Graceful failover is a separate protection. If an origin returns an HTTP 5xx error, the edge may serve an eligible cached response instead. It can keep an already cached page available while the origin has a problem. + +This does not repair application errors or create cached copies of responses that were never stored. Personalized pages, API calls that bypass the cache, and writes still depend on a working origin. See [Caching](/caching) for what Spacefast caches. + +## Data location + +The redundant copy lives in another region. An origin preference is not a guarantee that all site data stays in one geographic location. WP Cloud can store site data in both the United States and the European Union. + + diff --git a/content/infrastructure/ddos-protection.mdx b/content/infrastructure/ddos-protection.mdx new file mode 100644 index 00000000..a8902e0a --- /dev/null +++ b/content/infrastructure/ddos-protection.mdx @@ -0,0 +1,28 @@ +--- +title: DDoS protection +description: How edge routing, caching, and traffic classification protect your Space. +--- + +Spacefast sites inherit WP Cloud's platform-level protection against denial-of-service and distributed denial-of-service attacks. It operates without a setup step or a plugin in your Space. + +## Before traffic reaches your application + +Anycast routing distributes inbound requests across the edge network. The edge can serve eligible cached responses, classify suspicious traffic, and apply [rate limits](/infrastructure/rate-limiting) before requests consume origin resources. + +WP Cloud monitors the network and adjusts protections as traffic changes. Caching and filtering can absorb repeated or unwanted requests, but they do not guarantee that every application remains available under every attack. + +## Browser challenges + +[Defensive Mode](/infrastructure/defensive-mode) adds browser challenges during suspected bot or DDoS activity. Certain suspicious traffic can also receive a challenge independently of Defensive Mode. + +A challenge normally returns HTTP `403`. A rate limit normally returns `429`. Inspect [edge request logs](/infrastructure/edge-request-logs) to distinguish them before changing your application. + +For a known unwanted pattern, Spacefast also supports traffic rules through `sf rules`. A firewall rule can block a request or ask for a browser challenge. The [CLI reference](/cli/reference#sf-rules) lists the supported matches and actions. + +## During an incident + +Check whether legitimate visitors or integrations are failing, then preserve request samples. Include the affected hostname, UTC timestamp, URL, source IP address, complete `User-Agent`, and the response status when contacting Spacefast support. + +Send traffic directly to Spacefast's supplied DNS addresses where possible. An extra proxy can hide classification signals and change how visitors reach the edge. If you use one, include that detail in the incident report. + + diff --git a/content/infrastructure/defensive-mode.mdx b/content/infrastructure/defensive-mode.mdx new file mode 100644 index 00000000..8c671d13 --- /dev/null +++ b/content/infrastructure/defensive-mode.mdx @@ -0,0 +1,37 @@ +--- +title: Defensive Mode +description: Understand browser challenges and their effect on visitors and automated clients. +--- + +Defensive Mode challenges browsers before they reach a site, reducing unwanted automated traffic. Spacefast sites use WP Cloud's challenge system as part of their edge protection. + +A browser-check page does not, by itself, prove that Defensive Mode is active. Traffic classification and a Spacefast firewall rule can also trigger a challenge. + +## What visitors see + +Most challenges use an automated browser check. The visitor briefly sees a checking page, completes the check, and continues to the requested URL. The usual automated check can take up to five seconds. The platform can require a harder challenge with an action such as selecting a checkbox. + +Challenge responses use HTTP `403`. A client that cannot complete the challenge does not reach the requested page. HTTP `429` belongs to [rate limiting](/infrastructure/rate-limiting), a separate mechanism. + +API clients, webhooks, uptime monitors, and other automated services may be unable to solve a browser challenge. Test essential integrations when adding challenge rules. + +## What triggers a challenge + +| Trigger | Behavior | +| --- | --- | +| Manual Defensive Mode | WP Cloud supports an expiration from 30 minutes through seven days. Contact Spacefast support if site-wide protection is needed during an incident. | +| Automatic Defensive Mode | The infra can temporarily enable protection after a PHP resource-limit event. | +| Traffic classification | Suspicious or spoofed requests can be challenged even when Defensive Mode is off. | +| Spacefast firewall rule | A rule with the `challenge` action targets the request patterns you choose. | + +The platform challenge system cannot be fully disabled. Ending a manual mode or removing your own rule does not turn off classification challenges. + +For targeted rules, see [`sf rules`](/cli/reference#sf-rules). These rules are separate from the site-wide Defensive Mode setting in the WP Cloud API. + +## Help legitimate automation + +Send a specific, accurate `User-Agent`, such as `ExampleMonitor/1.0`. Do not impersonate a browser or a known crawler. Sending a Googlebot header from outside Google's network can itself trigger a spoofed-crawler challenge. + +An accurate header does not bypass protection. For a false positive, preserve the timestamp, source IP, full URL, and complete `User-Agent` from [edge request logs](/infrastructure/edge-request-logs), then contact Spacefast support. + + diff --git a/content/infrastructure/domain-verification-records.mdx b/content/infrastructure/domain-verification-records.mdx new file mode 100644 index 00000000..5d579ad5 --- /dev/null +++ b/content/infrastructure/domain-verification-records.mdx @@ -0,0 +1,40 @@ +--- +title: Domain verification records +description: Publish the exact TXT records needed to prove control of a Space hostname. +--- + +Most hostnames need no verification record: the address records are enough. A TXT record appears only when the hostname is already in use elsewhere. Copy the exact record names and values from your domain's setup instructions. + +## Get the records for your hostname + +```bash +sf domains add example.com --space my-site +sf domains diagnostics example.com --space my-site +``` + +The dashboard's domain setup also shows the required records. Add them wherever your DNS is managed. For the complete connection flow, see [Custom domains](/domains). + +There are two kinds, and they prove different claims. A WP Cloud record (`atomic-domain-…`) proves the hostname to WP Cloud when another host holds it. A `_spacefast-claim.` record moves a hostname away from another Space. Do not substitute one for the other or build a TXT value from an example. + +## When WP Cloud verification is required + +A hostname already used by another WP Cloud client can require an extra TXT record before it can attach to Spacefast. Spacefast retrieves that proof and includes it in the DNS instructions. + +The upstream value starts with `atomic-domain-` and belongs at the exact hostname being verified. The apex `example.com` and `www.example.com` can require separate values. + +Publish only the currently required WP Cloud verification value at each hostname. Multiple distinct TXT values starting with `atomic-domain-` at the same hostname can prevent verification, even when one matches Spacefast's instructions. Remove a superseded WP Cloud proof when moving the hostname. Keep unrelated TXT records, including email authentication records. + +A CNAME cannot coexist with the required TXT record at the same hostname. Follow Spacefast's address-record instructions instead. + +## Check and retain the proof + +```bash +sf domains check example.com --space my-site +sf domains diagnostics example.com --space my-site +``` + +DNS propagation can delay a successful check. Inspect every hostname in diagnostics; a verified apex does not prove that `www` is ready. + +Keep required verification records after setup so future checks can still prove control. A domain's verification state, routing readiness, and [TLS certificate](/infrastructure/tls-certificates) state are separate. A TXT record alone does not make the hostname live. + + diff --git a/content/infrastructure/edge-request-logs.mdx b/content/infrastructure/edge-request-logs.mdx new file mode 100644 index 00000000..68cd7b64 --- /dev/null +++ b/content/infrastructure/edge-request-logs.mdx @@ -0,0 +1,51 @@ +--- +title: Edge request logs +description: Inspect edge cache hits, rate limits, and browser challenges for a Space hostname. +--- + +Edge request logs show requests handled by the edge, including cached responses, rate limits, and browser challenges that may never reach your Space's origin. + +## Read edge requests + +```bash +sf logs edge --space my-site +sf logs edge --space my-site --hostname example.com --status 429 +sf logs edge --space my-site --status 403 --json +``` + +In the dashboard, open the Space's logs and select the edge view. For application output and origin requests, use [Access and runtime logs](/logs). + +Each query covers one assigned hostname and defaults to the primary hostname. Use `--hostname` to inspect a different mapped domain. An apex and its `www` hostname are separate queries. + +## Filters and time windows + +| CLI flag | Selects | +| --- | --- | +| `--hostname` | One hostname assigned to the Space | +| `--remote-addr` | Client IPv4 or IPv6 address | +| `--ja3-hash` | JA3 TLS fingerprint, exactly 32 hexadecimal characters | +| `--method` | HTTP method, such as `GET` | +| `--status` | HTTP response status | +| `--user-agent` | Exact complete `User-Agent` value | +| `--asn` | Source network's autonomous system number | +| `--since`, `--until` | ISO 8601 timestamps for the query window | + +`--remote-addr`, `--ja3-hash`, `--method`, `--status`, `--user-agent`, and `--asn` can be repeated to include more values. `--hostname`, `--since`, and `--until` take one value each. Values within one field use OR; different fields use AND. + +The default window is the past hour. A query can span at most one hour within the past 24 hours, and your plan's retention can shorten that window. Results arrive newest first. Use smaller windows to investigate busy periods. + +## What a record contains + +Records include `timestamp`, `hostname`, `method`, `path`, `status`, client IP, user agent, country, timing, and `cacheStatus`. Rate-limit details appear in `rateLimited` and `rateLimitReason`. Browser-check details appear in `defensiveMode.cookieStatus`, `challengeReason`, and `challengeResult`. + +Some details can be `null`. Access credentials in request URLs are redacted. + +## Pagination and truncation + +Spacefast returns up to 100 entries per page through `GET /v1/spaces/{spaceId}/logs/edge`. Continue with `pagination.nextCursor`, or the CLI's `--cursor`. A cursor refers to a fixed snapshot and expires after five minutes; the original window and filters still apply. + +The underlying query can return at most 5,000 records. An `edge_logs_truncated` warning means the snapshot may omit matches. Paging cannot recover records absent from that snapshot. Narrow the time range or add filters. Reaching the end of a cursor does not prove you exported every matching request. + +For traffic trends, use [Stats](/stats). Edge logs are individual request records, not a complete analytics export or a live stream. + + diff --git a/content/infrastructure/filesystem-permissions.mdx b/content/infrastructure/filesystem-permissions.mdx new file mode 100644 index 00000000..9c381d2b --- /dev/null +++ b/content/infrastructure/filesystem-permissions.mdx @@ -0,0 +1,32 @@ +--- +title: Filesystem permissions +description: Understand PHP filesystem controls and how they differ from published versions and application data. +--- + +A published Space version is an immutable snapshot. Changing files means publishing a new version. WP Cloud's PHP filesystem permissions are a separate control over writes made by PHP web requests. + +## What the infra control does + +WP Cloud supports the `php_fs_permissions` site setting: + +| Value | Effect on PHP web requests | +| --- | --- | +| `rw` | PHP can read and write the site's filesystem. This is WP Cloud's default. | +| `ro` | PHP can read files but cannot write them. Uploads, updates, and other file-writing operations can fail. | +| `loggedin` | Changes write behavior for some logged-in WordPress requests. The feature is evolving and requires testing against the site's actual workflows. | + +These are upstream capabilities, not fields to add to `sf.jsonc`. Spacefast manages the filesystem policy for its sites. Use [publish](/publish), [Storage](/object-storage), and the documented [WordPress tools](/wordpress) for the changes your application needs. + +## Read-only files do not mean read-only data + +A PHP filesystem restriction does not make the database read-only or prevent WordPress administration from changing database content. It also does not change SSH or SFTP access controls. + +For your Space, keep the two kinds of data separate. Published version files stay with their snapshot. Application records belong in [Database](/database), and mutable media belongs in [Storage](/object-storage). + +## Security incidents + +Read-only mode can help contain filesystem changes during an investigation. It does not remove malicious files or rotate compromised credentials. It can also break legitimate uploads, plugin updates, scheduled tasks, and integrations that write files. + +Contact Spacefast support before depending on an infra filesystem restriction. Check all affected application workflows after a policy change, including logged-out requests and background work. + + diff --git a/content/infrastructure/http-security-headers.mdx b/content/infrastructure/http-security-headers.mdx new file mode 100644 index 00000000..602484d5 --- /dev/null +++ b/content/infrastructure/http-security-headers.mdx @@ -0,0 +1,45 @@ +--- +title: HTTP and security headers +description: Set application security headers while leaving transport policy to the platform. +--- + +Set security headers for your published pages in `_headers` or the supported routing configuration. Spacefast manages transport headers, including HTTP Strict Transport Security, at the platform level. + +## Add a header policy + +Place `_headers` in the directory you publish. For example: + +```text _headers +/* + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + X-Frame-Options: DENY +``` + +This example prevents MIME sniffing, limits referrer information, and blocks framing. If your page must appear inside another site, choose an appropriate framing policy before copying it. + +Publish the change with your site. Rules apply to the URL the visitor requested, including when a rewrite serves another file. Matching header blocks accumulate. See [Redirects, rewrites, and headers](/routing) for ordering, removals, and validation. + +## Which settings belong where? + +| Header | What to know | +| --- | --- | +| `Content-Security-Policy` | Restricts scripts, frames, fonts, and other resources. Start with a report-only policy when practical and test required integrations. | +| `Access-Control-Allow-*` | Controls browser cross-origin access. Test the real requesting origin and any `OPTIONS` preflight. | +| `X-Frame-Options` | Controls framing. A restrictive policy can break embeds. | +| `X-Content-Type-Options` | Use `nosniff` to prevent MIME sniffing. | +| `Referrer-Policy` | Controls which referrer details accompany outgoing requests. | +| `Strict-Transport-Security` | Managed by the infra. Authored routing rules cannot replace it. | +| `Cache-Control` | Allowed for browser responses, with a platform-managed caching warning. Shared caching follows [Spacefast's caching rules](/caching). | + +Inspect the exact URL instead of assuming every response has the same headers. Application routes, published files, errors, and cached responses can take different paths. + +## Check the result + +```bash +curl -sS -D - -o /dev/null https://example.com/path/ +``` + +Test while logged out and repeat the request to inspect cached responses. A successful `curl` request does not prove that a browser permits a cross-origin response or that a Content Security Policy allows every required resource. Check the browser console and the application's real forms, scripts, embeds, and payment flows. + + diff --git a/content/infrastructure/ip-ranges.mdx b/content/infrastructure/ip-ranges.mdx new file mode 100644 index 00000000..aa572dea --- /dev/null +++ b/content/infrastructure/ip-ranges.mdx @@ -0,0 +1,33 @@ +--- +title: IP ranges +description: Use the DNS addresses Spacefast returns and account for changing outbound addresses. +--- + +Copy the DNS records Spacefast gives you for your hostname. Inbound addresses route visitors to the edge; they do not identify the server running your Space. + +## Inbound traffic + +Spacefast uses WP Cloud's Anycast network. The same published address can send visitors to different edge locations, usually near the requester, before a request reaches the origin. + +Connect a domain through the [custom domain flow](/domains). The CLI and dashboard return the required address and verification records. Use those exact values, including the hostname on each record. + +WP Cloud's default inbound load-balancer range is `192.0.79.128/26`, but approved addresses can differ between partner configurations. Do not select an address from that range yourself or copy one from another Space. Spacefast obtains the addresses for your setup. + +```bash +sf domains add example.com --space my-site +sf domains diagnostics example.com --space my-site +``` + +## Outbound connections + +A request your Space's origin makes to an external service uses an origin pool's outbound address. That address can change after [automated failover](/infrastructure/automated-failover), relocation, or replacement of the underlying site. + +Functions workers are different: they send from Cloudflare's shared network, so an allowlist built on the origin pool does not cover them. See [Functions outbound HTTP](/functions#outbound-http). + +Outbound addresses can be shared with other sites. An IP allowlist covering a complete infra range does not authenticate your application, and a single observed address is not a stable integration contract. Use the external service's authentication mechanism. If it also requires an IP allowlist, contact Spacefast support to discuss the requirement before depending on a fixed address. + +## Keep DNS and location separate + +A public DNS lookup tells you where to send inbound traffic. It does not reveal a permanent origin location or the address an outbound connection will use. Changing DNS is not how you select an origin region. + + diff --git a/content/infrastructure/meta.ts b/content/infrastructure/meta.ts new file mode 100644 index 00000000..57c6f40a --- /dev/null +++ b/content/infrastructure/meta.ts @@ -0,0 +1,20 @@ +import { defineMeta } from "blume"; + +export default defineMeta({ + title: "Infrastructure", + collapsed: true, + pages: [ + "automated-failover", + "ip-ranges", + "origin-edge-servers", + "server-specs-settings", + "ddos-protection", + "defensive-mode", + "rate-limiting", + "http-security-headers", + "filesystem-permissions", + "edge-request-logs", + "domain-verification-records", + "tls-certificates", + ], +}); diff --git a/content/infrastructure/origin-edge-servers.mdx b/content/infrastructure/origin-edge-servers.mdx new file mode 100644 index 00000000..e5b2c628 --- /dev/null +++ b/content/infrastructure/origin-edge-servers.mdx @@ -0,0 +1,30 @@ +--- +title: Origin and edge servers +description: How requests reach the edge, use the cache, and continue to the origin. +--- + +The edge receives requests close to your visitors. The origin stores site data and handles requests the edge cannot answer from cache. + +## A request through Spacefast + +1. Your hostname resolves to an Anycast address. Network routing takes the visitor to an edge location. +2. The edge applies traffic protections and can return an eligible cached response. +3. A request that needs the origin continues to the site running your Space. Spacefast serves the published files or runs the application behavior the request needs. + +Edge Cache and origin caching are separate layers. A cache hit at the edge avoids an origin request. An origin cache hit can avoid running WordPress again. See [Caching](/caching) for Spacefast's response and invalidation rules. + +## Origin regions + +WP Cloud operates origin locations in Amsterdam, Ashburn, Dallas, and Los Angeles, with edge locations across more regions. The public IP address does not identify the physical origin. + +Spacefast manages site placement. A WP Cloud origin preference is a provisioning choice, not a per-request routing rule or a Spacefast setting for moving an existing Space. WP Cloud can relocate sites for platform health and performance, and [failover](/infrastructure/automated-failover) can temporarily change the active origin. + +Site replicas can span the United States and the European Union. Do not infer a data-residency guarantee from an origin location, a DNS answer, or the edge that served one request. + +## Which logs show the request? + +[Edge request logs](/infrastructure/edge-request-logs) include requests handled at the edge, including cache hits and security challenges that never reach the origin. [Access and runtime logs](/logs) cover origin requests and application output. + +Use [Stats](/stats) to inspect traffic trends. A missing origin log entry does not mean the edge never received the request. + + diff --git a/content/infrastructure/rate-limiting.mdx b/content/infrastructure/rate-limiting.mdx new file mode 100644 index 00000000..fcc95011 --- /dev/null +++ b/content/infrastructure/rate-limiting.mdx @@ -0,0 +1,37 @@ +--- +title: Rate limiting +description: Diagnose visitor-facing 429 responses and distinguish edge limits from application capacity. +--- + +A request to your Space can receive HTTP `429` when traffic matches an infra rate limit or exceeds application capacity. Occasional limits on scanners and unwanted bots are expected. Investigate when legitimate visitors or integrations cannot continue. + +## Edge traffic limits + +WP Cloud classifies requests using signals such as request rate, URL patterns, user agent, proxy characteristics, and connection behavior. Limits can apply to a pattern, a site, or the wider platform. + +Thresholds can change as traffic changes. Do not build a client around an undocumented requests-per-second allowance. Send an accurate `User-Agent` and reduce repeated requests when a limit appears. + +## Resource limits are different + +A visitor-facing rate-limit page can also represent a PHP resource-limit event, recorded by WP Cloud as HTTP `599`. That means a PHP connection was not available in time; it does not require CPU usage to reach a maximum. + +Spacefast also limits concurrent application work. When that admission limit is reached, it returns `429` with `Retry-After`. Respect that header and retry with backoff. Repeating the same work immediately adds pressure. + +A [Defensive Mode](/infrastructure/defensive-mode) challenge uses `403` instead. Look at the request details before treating every rejected request as the same problem. + +## Find the affected requests + +```bash +sf logs edge --space my-site --status 429 +sf logs access --space my-site +``` + +[Edge request logs](/infrastructure/edge-request-logs) show cache handling, rate-limit reasons, and challenge details. Origin [access and runtime logs](/logs) help explain requests that reached your application. Compare the same hostname and time window. + +For an authorized crawler or integration, reduce its request rate, check for loops and repeated missing URLs, and confirm its identity header. If a legitimate workflow remains blocked, contact Spacefast support with exact request samples. + +## Management API limits + +Limits on calls to `api.spacefast.com` are a separate contract. See [API rate limits](/api/rate-limits) for response headers, quotas, and retry behavior. + + diff --git a/content/infrastructure/server-specs-settings.mdx b/content/infrastructure/server-specs-settings.mdx new file mode 100644 index 00000000..8e9c6305 --- /dev/null +++ b/content/infrastructure/server-specs-settings.mdx @@ -0,0 +1,33 @@ +--- +title: Server specifications and settings +description: Managed hardware, PHP settings, and the configuration boundaries for a Space. +--- + +Spacefast manages the servers behind your Space. You publish files and configure application behavior; the infra handles hardware, replication, and the shared edge network. + +## Underlying infra + +WP Cloud runs origin pools on AMD EPYC processors with NVMe storage and redundant 25 Gbit networking per server. Primary and secondary servers sit in different regions for [automated failover](/infrastructure/automated-failover). Hardware and software change as the platform upgrades pools. + +Those are pool specifications, not dedicated resources reserved for each Space. Your product entitlements come from your [plan limits](/limits). + +## PHP and compression + +WP Cloud's default PHP memory limit is 512 MB per request. The platform supports larger per-site values, but Spacefast manages the settings for its sites. A WP Cloud capability does not create a matching Spacefast configuration field. + +The infra supports Brotli and gzip compression. A client that accepts Brotli can receive it; gzip is available as a fallback. Compression headers belong to the platform, so do not set `Content-Encoding` in your published header rules. + +For application access to WordPress, use the documented [WordPress commands and Abilities](/wordpress). The underlying PHP configuration is not a promise that arbitrary uploaded PHP files will execute. + +## Configuration boundaries + +| Change | Spacefast path | +| --- | --- | +| Redirects, rewrites, and response headers | Publish `_redirects`, `_headers`, or the supported [routing configuration](/routing). | +| Application environment variables | Use [Environment variables](/environment-variables). | +| Runtime configuration | Use the documented [Zero runtime configuration](/zero-runtime). | +| Storage and resource entitlements | Check [Limits](/limits). | + +Spacefast rejects authored `.htaccess` and `.user.ini` files. You cannot replace the managed server configuration or `php.ini` through a publish. Use the supported configuration for the behavior you need. + + diff --git a/content/infrastructure/tls-certificates.mdx b/content/infrastructure/tls-certificates.mdx new file mode 100644 index 00000000..517cd2a2 --- /dev/null +++ b/content/infrastructure/tls-certificates.mdx @@ -0,0 +1,44 @@ +--- +title: TLS certificates +description: Automatic HTTPS, per-hostname certificate checks, and recovery from blocked issuance. +--- + +Spacefast provisions and renews HTTPS certificates for your Space's hostnames automatically. For a custom domain, issuance depends on correct DNS and a reachable validation path. + +## Provisioning and renewal + +WP Cloud manages certificates through Let's Encrypt and Google Trust Services. Issuance uses the ACME HTTP-01 challenge, so each hostname must resolve to the supplied infra addresses and allow validation over HTTP. + +The apex and `www` are validated independently. One can have a working certificate while the other is still pending. Check every hostname returned by your domain setup. + +Renewals happen automatically. Failed attempts are requeued with increasing delays, so correct the DNS or configuration problem before requesting another attempt. + +## Check a certificate + +```bash +sf domains diagnostics example.com --space my-site +``` + +The diagnostics report DNS, verification, and SSL state for each hostname. The UI and API use the familiar name SSL in some fields; the connection uses TLS. + +To inspect the certificate presented by a public hostname: + +```bash +openssl s_client -connect example.com:443 -servername example.com /dev/null | openssl x509 -noout -issuer -dates +``` + +Issuer names and chains can change. Do not pin an integration to a particular issuer common name. + +## When issuance is blocked + +Check that the hostname's address records match Spacefast's instructions. Remove old addresses that still send some requests elsewhere. If CAA records restrict certificate authorities, allow the authorities used for issuance. Check that an extra proxy or redirect does not block the HTTP validation request. + +After fixing the cause, use **Retry HTTPS** on the domain's page in the dashboard (it appears while a certificate is failing) or `POST /v1/domains/{domainId}/ssl/retry`. Retries are rate limited; repeated retries do not repair a DNS or CAA error. + +See [Custom domains](/domains#troubleshooting) for diagnostic codes such as `ssl_pending` and `ssl_renewal_blocked`. + +## Protocol support + +WP Cloud disables TLS 1.0 and TLS 1.1. Modern clients negotiate TLS 1.2 or TLS 1.3. Some TLS 1.2 ciphers remain for older-client compatibility, so evaluate scanner findings against the protocol and cipher the affected client actually negotiates. + + diff --git a/content/meta.ts b/content/meta.ts index 5a1250f9..bd345204 100644 --- a/content/meta.ts +++ b/content/meta.ts @@ -10,6 +10,7 @@ export default defineMeta({ "publish", "serve", "dynamic", + "infrastructure", "account", "reference", ], diff --git a/package.json b/package.json index f03777b8..4dd20154 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "blume": "2.2.6", - "zod": "4.6.0" + "zod": "4.6.5" }, "devDependencies": { "@spacefast/routing": "0.0.24", diff --git a/styles/Spacefast/spelling-exceptions.txt b/styles/Spacefast/spelling-exceptions.txt index 301e1788..33b925dd 100644 --- a/styles/Spacefast/spelling-exceptions.txt +++ b/styles/Spacefast/spelling-exceptions.txt @@ -5,11 +5,13 @@ accountless allowlist allowlisted alphanumerics +Anycast api apis approver approver's argv +Ashburn Astro async Automattic @@ -21,6 +23,7 @@ Blume's boolean booleans Bot's +Brotli browsable buildable burstable @@ -64,6 +67,7 @@ esbuild ETag exif extensionless +failover Favicon fetchable Figma @@ -72,8 +76,10 @@ followable forwardable frontmatter fsynced +Gbit github globals +Googlebot Gravatar grayscale Gridsome @@ -132,6 +138,7 @@ npx nullable Nullable Nuxt +NVMe oauth onboarding OpenAPI