set-up · nginx · with or without Docker
Nginx log analytics from the log nginx already writes
Nginx log analytics with no change to nginx for most sites. One command on your server finds the site's access log in nginx's own settings and sends each new line to your Skald, with or without Docker. Skald sorts every line into people, AI assistants, search engines, bots and your own traffic.
Nginx log analytics is reading the access log nginx writes for every request it answers, and turning it into visits, sources and pages. Skald reads nginx's default combined format, and its own extended format with the host, the time taken and the country.
30-day free trial · no card · we set it up within one working day · free to self-host
- Reads nginx.conf, conf.d/ and sites-enabled/
- shop.example.org is not your site: skipped
- server_name example.com: this block answers for your site
- status.example.net: skipped
$hosttried as example.com and www.example.com; one file is there
found in the web server's settings: /var/log/nginx/example.com.access.log
Sample settings. The one command reads them; it never edits them. A block with no access_log of its own writes to the http block's log.
[01] the log format
Most sites change nothing in nginx's log format. A few fields add something.
nginx's default format, called combined, already holds the six facts Skald needs: address, time, request, status, referrer and user agent. A field snapped on at the end of the line adds one more answer. Pick a field to see the line it writes and what it gives you.
the line nginx writes · sample
198.51.100.7 - - [23/Oct/2026:09:14:03 +0000] "GET / HTTP/2.0" 200 8312 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0) Chrome/130.0"
- What it gives
- Read as it is. Visits, sources, pages, AI crawlers and bots all come from these six facts.
- Who needs it
- Every site
- Read by Skald as
nginx_combined
Sample lines. A field goes inside the log_format quotes, at the very end. Then sudo nginx -t && sudo systemctl reload nginx tests the settings and reloads them. Old lines stay as they were; only new lines carry the field.
[02] which format is it?
Skald reads the first lines and lets every format try them
You do not tell Skald which format your log is in. It tries every format it knows on the first 200 lines, keeps the one that reads the most, and the richest wins a tie. A later line the winner cannot read is tried against the others. Pick a log.
nginx_extendednginx_combinedcaddy_jsonsix CDN and drain formats
detected nginx_combined · 10 of 10 lines read · day published
nginx's default. Nothing else reads these lines, so combined wins with all ten.
Scores from Skald's own detect step, run on ten sample lines of each log. The limit of unreadable lines is set per install; the default is 1%.
[03] one command, two ways to run
What the one command leaves on your nginx server
The command is the same on every server. It uses Docker when Docker works there, or else a systemd service, the background-service manager most Linux servers have, which needs Python 3.9 or newer. A server set up one way before keeps that way. Flip the switch and open the drawer.
/ what stays on the server
skald-shipa container, 128 MB of memory at most, user 10001; the log's folder is mounted read-only; Docker restarts it after a reboot/srv/skald/ship-tokenroot:adm, 0640: used, never printed, and it can only add lines to this one site/srv/skald/ship-state/its place in the log/srv/skald/ship.confthe log file and the send address, no token/usr/local/bin/skald-ship-checkchecks, changes nothing/usr/local/bin/skald-ship-uninstallremoves only what the command made
/usr/local/bin/skald-shipthe shipper, one Python fileskald-shipa system user with no login, in group adm so it may read the log/etc/skald-ship/tokenroot:skald-ship, 0640: used, never printed/etc/systemd/system/skald-ship.serviceruns with the system read-only to it, logs read-only, no new privileges/var/lib/skald-ship/state.jsonits place in the logskald-ship-check · skald-ship-uninstallin /usr/local/bin: check it, or take it away
- Never edits nginx. It reads nginx's settings and the log, nothing else.
- New lines only. It starts at the end of the log; older lines are not sent.
- Never lost. It saves its place only after Skald has taken a batch, and sends again after a failure, waiting 1 second, then twice as long each time, up to 5 minutes.
- Never twice. Batches are numbered, and Skald drops one it already has.
- Log rotation. logrotate is the tool that renames or empties the log each day and compresses old ones. The shipper follows both kinds of rotation. Keep
delaycompress, so the last rotated file is not compressed while it may still be read. - Small sends. Gzip batches of at most 2,000 lines or 4 MiB, over HTTPS.
Options go after sudo bash -s --: --log <path> names the file, --yes asks nothing, --dry-run shows every step and changes nothing.
[04] check it
One check lights a lamp for each thing that can go wrong
sudo skald-ship-check reads only and changes nothing. Each line says OK, or PROBLEM with the fix under it, and the end holds a block to send us, with no token and no address in it. Pick a fault.
✓ OK www.example.com writes to /var/log/nginx/example.com.access.log
fix All OK.
exit 0
Sample output, one line of the check at a time. The words are the check's own. A Next.js site whose log lacks the prefetch field gets an ADVICE line, which is not a problem and does not change the exit.
[05] other set-ups
Skald on the same nginx server, behind a CDN, and the limits
The one command fits most servers. Three set-ups change the plan, and a few things stay out of reach whatever you do.
Skald on the same server
No shipper at all. Run Skald in Docker and mount the log folder read-only, for example
-v /var/log/nginx:/logs:ro. Skald reads the log in place.Behind Cloudflare
A page Cloudflare serves from its cache never reaches nginx. Add the Skald Worker, a small script Cloudflare runs in front of your site: Skald keeps the Worker's cache hits and takes the rest from nginx.
One log per site
Each site needs its own
access_login its own server block. A log shared by several host names mixes their visitors, and the check says so.
What it does not do
- It does not edit nginx. The fields above are yours to add; the check only says when the Next.js one is missing.
- It sees only requests that reach nginx. Behind a CDN, cached pages need the CDN's own log or the Skald Worker.
- A plain combined line has no country. Skald looks the address up in a GeoIP country file (a list of which addresses are in which country) when one is set on the Skald server; without one, the country says Not recorded.
- It sends only lines written after the install. Older lines in the file are not sent.

keep reading
More on nginx log analytics
questions
Nginx log analytics FAQ
Straight answers, including what Skald does not do.
Do I have to change my nginx log format?
No, for most sites. Skald reads nginx's default combined format as it is. A Next.js site needs one field added at the end of the log_format, so a click inside the site counts as a page view. Skald's extended format, with the host, the time taken and Cloudflare's country, is optional.
Does it need Docker?
No. The one command uses Docker when Docker works on the server. Otherwise it installs the shipper as a systemd service, which needs Python 3.9 or newer. If neither is there, it stops and says what to install.
Will I lose lines when nginx's log rotates?
No. The shipper follows logrotate's renames and copytruncate, and it saves its place only after Skald has taken a batch. Keep delaycompress in the logrotate settings, so the last rotated file is not compressed while the shipper may still be reading it.
Can a line be counted twice if the network fails?
No. Batches are numbered and Skald drops a batch it already has. A batch that did not reach Skald is sent again, so a line is neither lost nor counted twice.
Several sites share one access log. Is that a problem?
Yes. Each site needs its own access_log line in its own server block, or the lines of the other sites are mixed in. sudo skald-ship-check says when the log is shared with other host names.
Where do the country figures come from?
From the log when it carries one: Skald's extended format records Cloudflare's country header as cc=. Otherwise from a GeoIP country file on the Skald server, when one is set. With neither, the country says Not recorded. It is never guessed.
$ tail -f access.log | skald
See what AI does with your site. Free for 30 days.
No card. We set up hosted Skald for you within one working day, or you self-host it free. Every line of your log, sorted and added up.