Overview
This guide builds a dedicated Upsun Cloud project that receives logs over HTTPS, parses them with Vector, and stores them in OpenSearch for search and analysis. Optionally, Vector also writes a compressed copy to Upsun object storage so you can keep OpenSearch retention short and archive cheaply. The project accepts two kinds of logs:- Fastly CDN logs, streamed from one or more Fastly services through an HTTPS logging endpoint, in a structured JSON format.
- Upsun application and service logs, forwarded from one or many Upsun projects through the
httploglog forwarding integration.
Prerequisites
You need the following before you start:- An Upsun organization where you can create projects, and the Upsun CLI installed and logged in (
upsun login). - Project admin access on every Upsun project whose logs you want to forward. Real-time log forwarding is a paid feature on Upsun, so confirm it’s enabled for those projects.
- An Upsun project served through Fastly. Upsun Support adds the Fastly logging endpoint for you through a ticket (Step 6).
- A domain or subdomain for the ingestion endpoint, such as
logs.example.com, that you can point at Upsun. gitandcurlon your workstation.
Step 1: Create the Flex project
Create an empty project in the same region as the projects and Fastly POPs you’ll send the most logs from. That keeps latency and egress down.cd log-ingestion.
The clone already contains a .upsun/local/ folder. That’s the CLI’s local cache for this project, so keep it out of git before you add anything else:
challenge file answers Fastly’s ownership check for HTTPS logging endpoints (see Step 6).
Step 2: Define apps, services, and routes
The project has two apps and one service. Both apps use the default OpenSearch relationship, and mounts keep their data writable at runtime. The build hooks download pinned releases of Vector, OpenSearch Dashboards, and Caddy. Their archives are kept in$PLATFORM_CACHE_DIR to reuse them across builds. Create .upsun/config.yaml:
.upsun/config.yaml
socket_family: tcpmakes Upsun hand each app a$PORTto listen on. Both configs read it from the environment.locationson the Vector app serve the static Fastly challenge file directly and pass every other request to Vector.authentication.enabled: trueturns on OpenSearch credentials. Upsun exposes them to both apps asOPENSEARCH_USERNAMEandOPENSEARCH_PASSWORD, alongsideOPENSEARCH_HOSTandOPENSEARCH_PORT.- Object storage isn’t used by default. If you add the optional archive, you enable it with a resources command, and Upsun adds the
OBJECT_STORAGE_*variables to the Vector app automatically.
dashboards/supervisor.conf to manage both processes:
dashboards/supervisor.conf
NODE_OPTIONS heap setting. Their output goes to the app log.
Create dashboards/opensearch_dashboards.yml:
dashboards/opensearch_dashboards.yml
dashboards/Caddyfile. Caddy listens on the Upsun port, asks for a username and password, and passes authenticated requests to Dashboards:
dashboards/Caddyfile
Authorization header. Upsun protects OpenSearch with plain HTTP basic auth, not the OpenSearch Security plugin. Dashboards uses opensearch.username only for its own startup tasks. For requests made on behalf of a person, it forwards that request’s Authorization header. Caddy checks the person’s Dashboards password, then swaps in the OpenSearch credentials, so every OpenSearch call is authenticated. Without the swap, Dashboards loads but every page returns 401 from OpenSearch.
Step 3: Configure Vector
Vector listens on one HTTPS route and sorts traffic by URL path. Fastly posts to/fastly, and each Upsun project posts to /upsun/<project-id>. Vector checks HTTP basic auth on every request.
First, store the ingestion credentials as sensitive project variables so they never live in git:
OPENSEARCH_USERNAME and OPENSEARCH_PASSWORD that Vector sends on every OpenSearch call.
Then create vector/vector.yaml:
vector/vector.yaml
- One source, many senders. Upsun gives each app a single port, so one
http_serversource takes all traffic, and theroutetransform splits it by path. - Per-source parsing. Fastly’s
timefield has no time zone, so it’s parsed as UTC into@timestamp. Upsun events get anupsun_projectfield from Upsun’s owncontainer.projectmetadata, which is how you tell projects apart. - Daily indices. Events land in
logs-fastly-YYYY.MM.DDandlogs-upsun-YYYY.MM.DD. Daily indices make retention a matter of deleting old indices (see Security, retention, and cost). - Disk buffer. If OpenSearch is slow or restarting, Vector queues up to 1 GiB on disk instead of dropping logs.
parse_upsun reads the event time from Upsun’s timestamp_ns field. It also spots Nginx lines, which arrive as <container> nginx: <log line>, and parses them into an nginx object (nginx.client, nginx.path, nginx.status, …) with log_file set to access.log or error.log. Keeping them in their own object stops them clashing with Upsun’s host and timestamp fields, which OpenSearch would otherwise reject as a mapping conflict.
Vector 0.57 and later. Two defaults changed in 0.57. First, ${VAR} references in the config are no longer filled in unless Vector starts with --dangerously-allow-env-var-interpolation, which the start command in Step 2 includes. Every credential and port here comes from Upsun environment variables, so leave it on. Second, index names and S3 key prefixes built from templates must start with fixed text, which is why they begin with logs- and logs/.
Step 4: Deploy and size resources
Commit and push. The first deploy builds both apps and provisions OpenSearch:data mount defaults to 512 MB, but the OpenSearch sink reserves a 1 GiB disk buffer. Vector refuses to start if the buffers don’t fit (Mountpoint '/app/data' has total capacity of … bytes), which shows up as a 502 on the ingestion URL. 2 GB covers the default config. If you add the optional archive sink, which has its own 1 GiB buffer, use vector:3072.
Use this table as a starting point, then adjust from real usage with upsun metrics:
Above about 50 GB a day, shorten hot retention, add the optional object storage archive (pattern A), or talk to your account team about a larger setup.
Match the Dashboards version. Dashboards refuses to connect to a different OpenSearch version. Check what Upsun runs, then set
OSD_VERSION in .upsun/config.yaml to the same number and push again:
https://{default}/ and Dashboards is https://dashboards.{default}/. Set your ingestion hostname as the default domain and add a CNAME for each hostname, pointing at the target that upsun domain:add prints:
upsun url --primary to open the primary route, or upsun url --pipe to list the generated URLs, which look like this:
logs.example.com or dashboards.logs.example.com, including the Fastly endpoint and challenge URL. They change only if the environment is renamed or recreated.
Set the Dashboards password
Caddy checks a username and password stored as project variables. The app hashes the password each time it starts, so you only ever handle the plain value:https://dashboards.logs.example.com/ and, at the browser’s login prompt, enter admin and the password from DASH_PASS. To change it later, run upsun variable:update --level project --value "<new password>" env:DASHBOARDS_PASSWORD.
Upsun’s environment-level HTTP access control in the Console isn’t used here: it applies to every route, so it would also block Fastly and your log senders.
Step 5: Verify the pipeline
Prove the whole chain works with a sample event before you connect Fastly or any Upsun project. Come back to parts 3 and 4 once real logs are flowing.- Send a test event. This should return
200:
401 means the credentials are wrong. A 404 means the path didn’t match a route.
2. Check that indices exist:
- Inspect raw events (optional). To see exactly what Fastly or Upsun sends before you adjust the parsing, add this to the top of
vector.yaml, push, then tap the stream:
- Explore in Dashboards. Open
https://dashboards.logs.example.com/app/homeand sign in at the browser prompt. If you see a Dashboards 404 page ({"statusCode":404,"error":"Not Found"}), the browser requested the wrong path; open/app/homedirectly. On first load, Dashboards may offer a “New! Enhanced Discover experience” popup. Click Dismiss: this guide uses the standard Discover, and those settings aren’t needed. On the “Welcome to OpenSearch Dashboards” screen, click Explore on my own (Add data only offers sample datasets).
logs-fastly-* and logs-upsun-*, both with @timestamp as the time field. Then try these in Discover:
Step 6: Stream Fastly logs
Fastly sends logs to an HTTPS endpoint only after the endpoint proves it expects them, then posts batches of newline-delimited JSON.Publish the ownership challenge
Fastly fetcheshttps://logs.example.com/.well-known/fastly/logging/challenge and looks for the SHA-256 hash of your service ID. Put one hash per line, one line per Fastly service:
shasum -a 256, which works on macOS and Linux. sha256sum doesn’t exist on macOS, and the command above would silently write an empty file. Use the service ID (for example SU1Z0isxPaozGVKXdv0eY), not the service name.
Don’t know your Fastly service ID? Upsun Support can tell you. Until then, put a single * in the file, which accepts any Fastly service; Vector’s password check still protects the endpoint.
Confirm it’s served without auth: curl https://logs.example.com/.well-known/fastly/logging/challenge.
Request the HTTPS logging endpoint
Upsun manages the Fastly service in front of your project, so Upsun Support adds the logging endpoint for you. Once the challenge file is live, open a ticket from the Upsun Console with the text below, filled in with your values. Generate the header value first:<your-service-name> with a readable name for the site:
timeis the request start in UTC. Vector copies it into@timestamp.service.ididentifies the Fastly service, so many services can share one endpoint and you can filter on it.ja3_fingerprintandja4_fingerprintare only filled on the first Fastly hop (the edge). On shield nodes they’re empty andserver.is_shieldistrue.
Step 7: Forward logs from Upsun projects
Each source project gets anhttplog log forwarding integration that posts to its own path on the ingestion endpoint. Use the source project’s own ID in the path. Upsun also stamps the project ID on every event, and Vector stores it as upsun_project.
Forwarding sends everything that goes through the system journal of each app container. On Upsun Cloud that includes Nginx’s access and error logs, so one integration per project covers the usual /var/log files:
This works the same for PHP (Drupal, WordPress, Magento), Node.js, Python, and decoupled projects. Nothing needs to change in the source app’s code or
.upsun/config.yaml.
One project
Many projects
List the project IDs in a file, one per line, and loop:integration:add refuses the httplog type.
Why not syslog? Upsun also supports syslog forwarding, but syslog needs a raw TCP or TLS port. Upsun apps are only reachable over HTTP(S) through the router, so this project accepts httplog only.
What arrives and how to find it
Each forwarded event carries Upsun’s metadata. The useful fields are:
Nginx lines are parsed by
parse_upsun (Step 3). Access lines get log_file: "access.log" plus nginx.client, nginx.method, nginx.path, nginx.protocol, nginx.status, nginx.size, nginx.referer and nginx.agent. Error lines get log_file: "error.log" plus nginx.message, and nginx.severity, nginx.client, nginx.request and nginx.host when the line includes them.
After the first events arrive, refresh the index pattern so the new fields show up: go to Dashboards Management → Index patterns, open logs-upsun-* and click the refresh icon. Then try these in Discover:
Add
container.service, nginx.client, nginx.method, nginx.path, nginx.status and nginx.agent as columns, then Save the search for one-click access.
Requests served from Fastly’s cache never reach the app, so they aren’t in the app’s access log. Use logs-fastly-* for the full edge view.
Other file logs
httplog doesn’t forward logs your app writes to its own files, or post-start.log. Send those messages to stdout or syslog instead: Drupal’s syslog module, WordPress’s error_log() with error_log = syslog, or Python’s and Node.js’s standard loggers writing to stdout. They then arrive through the same integration with no extra setup.
Security, retention, and cost
Security
Three sets of credentials:
OpenSearch has no authentication by default on Upsun, so keep
authentication.enabled: true even though it’s only reachable internally.
Retention
Add this cron job to thevector app in .upsun/config.yaml. It deletes OpenSearch indices older than 14 days:
logs-* can replace the opensearch-retention cron.
Cost
- OpenSearch disk is the biggest line. Shorten hot retention before adding disk.
- Drop fields you never query. A
del(.field)in theparse_fastlytransform shrinks every document. - Sample noisy traffic. Vector’s
sampletransform can keep, say, 1 in 10 successful static-asset hits while keeping every error. - Object storage is billed on allocation. 512 GB is the minimum, so compare the allocated storage cost with your expected OpenSearch retention before enabling an archive.
- Avoid long-lived preview environments. A branch of this project clones OpenSearch and runs a second ingest stack. Point senders only at
main.
Optional: Archive logs to object storage
Upsun object storage is an S3-compatible bucket attached to one app. Compressed JSON in a bucket costs far less per GB than OpenSearch disk, so it’s a good home for logs you must keep but rarely search. Choose one of two patterns:Enable the bucket
Object storage is sized in 512 GB steps (minimum 512 GB, maximum 10 TB) and billed on allocated size, not usage. Attach it to the Vector app:vector). Upsun injects OBJECT_STORAGE_HOST, OBJECT_STORAGE_PORT, OBJECT_STORAGE_ACCESS_KEY, OBJECT_STORAGE_SECRET_KEY and OBJECT_STORAGE_REGION into the Vector app. The endpoint is internal only, so no one outside the project can reach it.
Add the archive sink
Raise the Vector app’s disk for the second buffer (upsun resources:set --disk vector:3072), then append this sink to vector/vector.yaml:
opensearch sink, the dashboards app, the opensearch service, the dashboards.{default} route and the opensearch relationship from the Vector app.
Things to know
- No lifecycle rules. Upsun object storage doesn’t support lifecycle policies, versioning, object lock, server-side encryption or bucket policies. Expire old objects with the cron job below.
- Deletion is permanent. Setting the size to
0deletes the bucket and everything in it on the next deployment. - Reading archives. Use
upsun ssh -A vectorand the AWS CLI with--endpoint-url "http://$OBJECT_STORAGE_HOST:$OBJECT_STORAGE_PORT"to list or download files. To search old logs, download a day’s prefix and load it into your tool of choice, or re-ingest it into OpenSearch.
Expire old archives
Add the AWS CLI and a second cron job to thevector app, next to opensearch-retention. This one deletes the archive day that just aged out, here 400 days:
Troubleshooting
Start with the Vector app log:upsun log -A vector app --tail.
Expected and harmless:
Transform "route_by_path._unmatched" has no consumersat Vector startup. Requests to paths other than/fastlyor/upsun/<id>are dropped on purpose.- A new index takes a few seconds to appear. Vector batches events before sending them. If
_cat/indicesis empty right after the first test event, wait 10 to 20 seconds and check again. vector tapsays the API isn’t reachable. Theapi:block must start at column 0 invector.yaml, level withdata_dir:, and you must push after adding it.
Error ... at Fetch and then 502s. Check upsun log -A dashboards app. JavaScript heap out of memory means Dashboards ran out of memory, usually on a Discover search over a long time range. Upsun supplies the Node heap setting through NODE_OPTIONS, and Supervisor restarts Dashboards if it exits. If it keeps happening, raise the app’s size, for example upsun resources:set --size dashboards:2, and keep Discover time ranges short.
Upsun logs stop after the ingestion endpoint was down. If Vector was stopped or failing for a while, for example during a bad config push, source projects can stop forwarding even after it recovers. Today’s logs-upsun-* docs.count stays flat. Redeploy each source project to restart forwarding: upsun redeploy -p <source-project-id> -e main.