Skip to main content

Logging

The Logging page lists your websites, with two live views for each:

ViewContains
Access LogOne record per request, including those the edge answers itself, e.g. one stopped by a rate limit.
Error LogWhat went wrong answering those requests: an application that failed, a permission problem, a broken rewrite rule.

A view streams the log into a read-only terminal in your browser and follows new records as they arrive, one line per record in the classic web server format, with times in UTC.

The log files​

The live view has no search box and no download button. For anything more, work with the files in a terminal or over SSH.

They're in your account's log directory, /home/<account>/log, named after the website: access_log+www.example.com and error_log+www.example.com.

The log directory is not in your home directory

Your home directory is /home/<account>/home, and the log directory sits next to it, so ~/log doesn't exist. In a shell, "${HOME%/*}/log" is the log directory. The file manager shows only your home directory, so it can't open the log files.

The examples on this page start in the log directory:

cd "${HOME%/*}/log"

Every day, the server moves old records to the archive directory as zstd-compressed files and keeps them for 15 days. Read them with zstdcat, e.g. zstdcat archive/access_log+www.example.com+* | weblogconv.

The record format​

Both files are JSON Lines: each line is one JSON object, one record. The keys follow the OpenTelemetry semantic conventions where a name exists. A key without a value is left out. All times are UTC.

An access record (one line in the file):

{"timestamp":"2026-10-18T07:03:12.481Z","otel.event.name":"elpan.http.access","http.request.method":"POST","url.scheme":"https","url.path":"/wp-login.php","server.address":"www.example.com","server.port":443,"client.address":"203.0.113.7","network.protocol.name":"http","network.protocol.version":"2","http.response.status_code":429,"http.request.size":918,"http.response.size":1834,"http.response.body.size":1650,"user_agent.original":"Mozilla/5.0 (X11; Linux x86_64)","elpan.request.id":"6f1c0e3a9b2d4c5e8f7a1b2c3d4e5f60","elpan.request.duration":0.0,"elpan.answered_by":"edge","elpan.limit_req.status":"REJECTED"}

The access record keys you'll use most:

KeyValue
timestampWhen the request started, in UTC, with milliseconds.
otel.event.nameelpan.http.access for an access record.
http.request.methodThe method, e.g. GET. An unusual method is _OTHER, with the original in http.request.method_original.
url.path, url.queryPath and query string as the client sent them. The query has no ?.
server.addressThe hostname the client asked for. For a website with wildcard subdomains, the subdomain's name.
client.addressThe client's IP address.
network.protocol.version1.0, 1.1, or 2.
http.response.status_codeThe status code, as a number.
http.response.body.sizeThe body size the client got, in bytes.
user_agent.originalThe client's User-Agent header.
http.request.header.refererThe Referer header, as a one-item list.
user.nameThe user your application named. See Log your application's users.
elpan.auth.userThe user the web server's authentication accepted, e.g. with access control.
elpan.request.idThe request ID. See Follow a request to its errors.
elpan.request.durationSeconds the server took to answer.
elpan.answered_byorigin if the request reached your website's web server, edge if the edge answered it.
elpan.limit_req.status, elpan.limit_conn.statusOnly when a rate or connection limit acted on the request, e.g. REJECTED.

The error record keys:

KeyValue
timestampThe time of the error, in UTC, with microseconds.
otel.event.nameelpan.http.error for an error record.
severity_textThe web server's level: error, warn, notice, and the others.
severity_numberThe same level as an OpenTelemetry number: error is 17, warn is 13.
bodyThe full message. A message from PHP starts with AH01071: Got error '.
client.addressThe client's IP address.
elpan.request.idThe ID of the request that caused the error.
elpan.httpd.moduleThe part of the web server that wrote the message, e.g. proxy_fcgi.
elpan.httpd.message_idThe code at the start of the message, e.g. AH01071.

A program that reads the records must ignore keys it doesn't know; a later version can add new ones.

Search the files​

Use jq, which is on every server:

# The last 20 requests that failed with a status of 500 or more
jq -c 'select(."http.response.status_code" >= 500)' access_log+www.example.com | tail -20

# The number of requests for each status code
jq -r '."http.response.status_code"' access_log+www.example.com | sort | uniq -c | sort -rn

# The requests that a rate limit stopped
jq -c 'select(."elpan.limit_req.status" == "REJECTED")' access_log+www.example.com

# The messages of the error log since 18 October 2026 (UTC)
jq -r 'select(.timestamp >= "2026-10-18") | .body' error_log+www.example.com
Lines in the old format

On the day the server switched to JSON records, a file can also hold some lines in the old text format, and jq stops at such a line. Add -R and fromjson? to skip them: jq -cR 'fromjson? | select(."http.response.status_code" >= 500)' access_log+www.example.com.

Requests that the edge answers​

The edge receives requests from the internet and passes them to your website's web server. The ones it answers itself are in your access log too, with these status codes:

StatusWhat happened
444The edge closed the connection without an answer: the request looked like a scan for a known weakness, e.g. /.git/config.
403 on a gated pathThe edge sent the bot challenge page: the visitor had no valid pass yet.
429One client sent too many requests or opened too many connections in a short time. elpan.limit_req.status or elpan.limit_conn.status says which limit acted.
499The client closed the connection before the answer was ready.
502Your website's web server didn't answer. The visitor got the edge's error page.
504Your website's web server didn't answer in time.
200 for /.well-known/acme-challenge/The edge answered a validation request for a certificate.
note

A client the server blocks for abuse is stopped before the edge, so its requests are in no log.

The access log describes the request as the client saw it: the protocol version is the client's, and the sizes are what the client sent and got.

Follow a request to its errors​

Each request gets one ID, elpan.request.id, of 32 hexadecimal digits, shared by its access record and every error record it caused. The same ID also appears:

  • on the error pages the server shows to visitors, so a visitor can read it to you;
  • in your application, as the UNIQUE_ID environment variable and the X-Request-ID request header ($_SERVER['UNIQUE_ID'] and $_SERVER['HTTP_X_REQUEST_ID'] in PHP).

To find the errors of the last request that failed with a 500:

id=$(jq -r 'select(."http.response.status_code" == 500) | ."elpan.request.id"' access_log+www.example.com | tail -1)
jq -r --arg id "$id" 'select(."elpan.request.id" == $id) | .body' error_log+www.example.com

Convert the records to text: weblogconv​

weblogconv turns the records back into the classic text formats, for tools that read the Combined Log Format, e.g. GoAccess and AWStats, and for your own scripts that read the old format. man weblogconv has the details.

weblogconv access_log+www.example.com | tail -20
tail -f access_log+www.example.com | weblogconv
weblogconv --tz Asia/Jakarta error_log+www.example.com
OptionEffect
no --formatAn access record becomes combined, an error record text.
--format combinedThe Combined Log Format.
--format commonThe Common Log Format: combined without referer and user agent.
--format elpanThe access log's format before the switch to JSON.
--format textAn error line in the Apache web server's layout.
--user auth, --user app, --user eitherWhich user goes into the user field: elpan.auth.user, user.name, or the first of the two with a value (the default).
--tz <zone>Times in that time zone, e.g. Asia/Jakarta, instead of UTC.

A line that isn't a record passes through unchanged. Every value is escaped, so a value from a visitor can't change a line's fields or send control characters to your terminal.

What is somewhere else​

note

This page covers websites only. Other activity has its own, more structured views:

  • Mail. Delivery history and sign-in activity are on the traffic log pages for email.
  • App servers and containers. Each has a Logs button on its own page that streams the program's output. See App Servers and Containers.