What you are setting up, in plain terms
Graylog gathers the logs your servers, firewalls and apps produce, so you can search all of them from one screen and get alerted when something looks wrong. It needs three programs running side by side:
- Graylog, which receives messages, runs your processing rules and shows you the web interface on port 9000.
- MongoDB, a small database for accounts, dashboards, streams and alerts. On Graylog 7.2 it must be version 7.x up to 8.2.x.
- The Graylog Data Node, which keeps the messages and searches them. It runs OpenSearch for you and looks after the certificates between the parts. You may run your own OpenSearch instead (1.1.x to 3.7.x for Graylog 7.2), but Graylog recommends the Data Node.
How big a server your log volume needs
Graylog's smallest recommended layout, which it calls Core, splits the work across two servers. Its diagram gives each one a size, and its planning page gives two rules for disk:
| What | Graylog's number | In practice |
|---|---|---|
| Graylog and MongoDB | 8 cores, 16 GB memory | The part that receives and processes |
| Data Node | 8 cores, 24 GB memory | The part that stores and searches |
| Space for stored messages | Logs per day x days kept x 120% | The 20% covers traffic spikes |
| Journal (the waiting room for new messages) | 3 to 5 times one day of logs, on SSD | Ships at 5 GB or 12 hours by default |
By our own sums, if your systems produce 20 GB of logs a day and you keep them for 30 days, you need about 720 GB for messages plus 60 to 100 GB for the journal. Put both halves on one machine and you need 16 cores and 40 GB of memory between them. Graylog says plainly that this small layout has no spare: if a server goes down, collection and search stop until it is back.
The server that fits
Logs are written all day, so memory and SSD space count for more than a fast processor. Among our dedicated servers the two-processor machines are the natural home:
- Two Xeon E5-2630L v3 processors give you 16 cores, with memory sizes up to 256 GB. Pick 48 GB or more and you clear the 40 GB sum with room to spare (our margin, not Graylog's).
- Two Xeon Gold 6130 processors give you 32 cores, for heavy processing rules, or a pair of these to run the two Core halves on separate machines.
- Keeping months of logs? Have a server built with more drive bays, up to 24, in RAID 10. A custom build takes 4 to 24 hours when we hold the parts, and 5 to 10 working days when they have to be bought in.
A ready server is yours within 30 minutes of approval. On rack servers and blades you get a console you open yourself, so a firewall slip never locks you out. Every port is unmetered, so a noisy month of logs leaves your invoice alone. You manage the server; we keep the hardware and the network running. X-Gene ARM cartridges are out (Ubuntu 16.04 or 18.04 only).
Up and running: the install
You will need Ubuntu 24.04 on the server and Docker Engine with its Compose plugin, installed from Docker's own repository. We use Graylog's example Compose file, the one its Docker guide starts from. Be aware that the README in that repository calls these files material for tests and demos and untested in production; Graylog's Ubuntu packages are the alternative if that worries you.
Paste the block below as root. It turns the firewall on with SSH open, raises a kernel setting the Data Node needs (vm.max_map_count), and refuses to go on unless your Docker Engine version is at least 28 (so ports tied to 127.0.0.1 really stay private), this is its first run (so your secrets are never replaced) and every port in Graylog's file is tied to 127.0.0.1. Then it asks for your admin password without showing it, saves both secrets in a file only root can read, and starts everything. Graylog's file uses restart: "on-failure", which is not a dependable way to bring the stack back after a reboot; the inputs step adds a service for that.
(
set -euo pipefail
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw allow OpenSSH && ufw --force enable
echo 'vm.max_map_count=262144' > /etc/sysctl.d/99-graylog.conf
sysctl --system >/dev/null
v=$(docker version -f '{{.Server.Version}}')
[ "${v%%.*}" -ge 28 ] || { echo "STOP - Docker Engine 28+ needed"; exit 1; }
[ ! -e /opt/graylog/.env ] || { echo "STOP - .env exists, keeping it"; exit 1; }
mkdir -p /opt/graylog && cd /opt/graylog
curl -fsSo docker-compose.yml https://raw.githubusercontent.com/Graylog2/docker-compose/main/open-core/docker-compose.yml
if grep -E '^ *- "([0-9]+:|0\.0\.0\.0|\[)' docker-compose.yml; then
echo "STOP - a port above is not on 127.0.0.1"; exit 1; fi
s=$(openssl rand -hex 48); [ ${#s} -eq 96 ]
read -rsp 'Graylog admin password (12+ chars): ' p; echo
[ ${#p} -ge 12 ] || { echo "STOP - too short"; exit 1; }
h=$(printf %s "$p" | sha256sum | cut -d' ' -f1)
install -m 600 /dev/null .env
printf 'GRAYLOG_PASSWORD_SECRET="%s"\nGRAYLOG_ROOT_PASSWORD_SHA2="%s"\n' "$s" "$h" > .env
docker compose -f /opt/graylog/docker-compose.yml up -d
)
Where we differ from Graylog's guide, in full: we create .env ourselves rather than copying .env.example, generate the secret with openssl rand -hex 48 rather than with the /dev/urandom pipeline Graylog shows, and read your password with read -s, where the guide's command prints it. Keep the secret forever; the example file warns that changing it breaks sessions and encrypted settings.
Finishing setup privately
On first start Graylog opens a setup screen with a one-time password, reachable only through SSH from your computer:
- Find the one-time password on the server:
cd /opt/graylog && docker compose logs graylog | grep -i initial. - From your computer, connect with your usual login and forward the port:
ssh -L 9000:127.0.0.1:9000 -N yourname@server-ip. - Open
http://localhost:9000in your browser (the Compose file's external address), useadminand the one-time password, and let the setup screen create its certificate authority and set up the Data Node. - When Graylog has started, your
adminaccount works with the password you chose during the install. Changing it later means a new hash in.env, thencd /opt/graylog && docker compose up --detach --force-recreate. - Give each colleague a named account, and keep
adminfor emergencies.
Keep the interface private: Graylog's Ubuntu guide warns that internal services belong off the internet.
Letting your devices send logs, and nobody else
Your devices send logs to inputs, which you add in the web interface (System, then Inputs): syslog on port 5140, GELF on 12201. The catch when you open those ports: anything Docker publishes over IPv4 skips ufw. So this block first adds a filter to the DOCKER-USER chain, admitting only the addresses in /etc/graylog-senders, loads it again each time Docker comes up, and opens 5140 and 12201 last. It also adds graylog-stack, a small service that starts Graylog at boot and after Docker restarts, always after the filter. Rather than relying on Docker's restart policy, we start Graylog from a service ordered after the filter.
(
set -euo pipefail
touch /etc/graylog-senders
cat > /usr/local/sbin/graylog-inputs.sh <<'EOF'
set -eu
EXT=$(ip -o -4 route get 1.1.1.1 | sed -n 's/.* dev \([^ ]*\).*/\1/p')
[ -n "$EXT" ]
iptables -N GRAYLOG-IN 2>/dev/null || true
iptables -F GRAYLOG-IN && iptables -A GRAYLOG-IN -j DROP
for s in $(grep -Ev '^ *(#|$)' /etc/graylog-senders); do
iptables -I GRAYLOG-IN -s "$s" -j RETURN; done
for p in 5140 12201; do for t in tcp udp; do
r="-i $EXT -p $t -m conntrack --ctorigdstport $p -j GRAYLOG-IN"
iptables -C DOCKER-USER $r 2>/dev/null || iptables -I DOCKER-USER $r
done; done
EOF
S=/etc/systemd/system; O='[Service]\nType=oneshot\nRemainAfterExit=yes\n'
printf "[Unit]\nAfter=docker.service\nRequires=docker.service\nPartOf=docker.service\n$O%s\n[Install]\nWantedBy=docker.service\n" \
'ExecStart=/bin/bash /usr/local/sbin/graylog-inputs.sh' > $S/graylog-inputs.service
printf "[Unit]\nAfter=graylog-inputs.service\nRequires=graylog-inputs.service\n$O%s\n[Install]\nWantedBy=multi-user.target\n" \
'ExecStart=/usr/bin/docker compose -f /opt/graylog/docker-compose.yml up -d' > $S/graylog-stack.service
systemctl daemon-reload && systemctl enable --now graylog-inputs && systemctl enable graylog-stack
grep -q GRAYLOG-IN <<<"$(iptables -S DOCKER-USER)" || { echo "STOP - filter not loaded"; exit 1; }
sed -i -E 's/"127\.0\.0\.1:(5140|12201):/"\1:/' /opt/graylog/docker-compose.yml
systemctl restart graylog-stack
)
Write your senders' IPv4 addresses or ranges into /etc/graylog-senders, one per line, and run systemctl restart graylog-inputs; until then nothing from outside gets in. The filter watches the interface your default route uses, so add rules for any other interface your devices arrive on. With no ufw rule added, the ports stay closed on IPv6 while IPv6 networking inside Docker is off, as it ships; turn that on and you need the same rules in ip6tables. Prefer TCP where you can: Graylog notes that TCP delivery is acknowledged and UDP is not, and suggests TLS on inputs.
Keeping a copy you can actually restore
Graylog wants settings, MongoDB and stored messages backed up together and restored to one moment and version, so the scripts pause Graylog briefly each night. The first script stops Graylog, packs /opt/graylog with .env, the files from the steps above and all four Docker volumes, checks the archive, and starts Graylog again whatever happens. The second script restores an archive after saving today's state.
cat > /usr/local/sbin/graylog-backup.sh <<'EOF'
set -euo pipefail; umask 077; cd /
C=/opt/graylog/docker-compose.yml; B=/var/backups/graylog; f=$B/graylog-$(date +%F-%H%M%S).tar.gz
mkdir -p -m 700 "$B"
trap 'docker compose -f "$C" start' EXIT
docker compose -f "$C" stop
tar --numeric-owner -czf "$f.part" opt/graylog etc/graylog-senders etc/sysctl.d/99-graylog.conf \
etc/cron.d/graylog-backup etc/systemd/system/graylog-{inputs,stack}.service usr/local/sbin/graylog-*.sh \
var/lib/docker/volumes/graylog_{mongodb_data,mongodb_config,graylog-datanode,graylog_data}
tar -tzf "$f.part" >/dev/null; mv "$f.part" "$f"; echo "ok $f"
[ "${1:-}" != prune ] || find "$B" -name 'graylog-*.tar.gz' -mtime +6 -delete
EOF
cat > /usr/local/sbin/graylog-restore.sh <<'EOF'
set -euo pipefail
F=${1:?}; D=/var/lib/docker/volumes; L=$(tar -tzf "$F")
V="mongodb_data mongodb_config graylog-datanode graylog_data"
for v in $V; do [ -d "$D/graylog_$v/_data" ]
grep -q "^var/lib/docker/volumes/graylog_$v/_data/" <<<"$L"; done
bash /usr/local/sbin/graylog-backup.sh
docker compose -f /opt/graylog/docker-compose.yml down
for v in $V; do find "$D/graylog_$v/_data" -mindepth 1 -delete; done
tar -C / --numeric-owner -xzf "$F"
chown -R 1100:1100 "$D/graylog_graylog_data/_data"
systemctl daemon-reload && sysctl --system >/dev/null
systemctl enable graylog-inputs graylog-stack && systemctl restart graylog-inputs
grep -q GRAYLOG-IN <<<"$(iptables -S DOCKER-USER)"
systemctl restart graylog-stack
EOF
Do the inputs step first; the backup copies its files (or drop them from the tar line). For a nightly run with week-old copies cleared, add cd /etc/cron.d && echo '15 2 * * * root cd /usr/local/sbin && bash graylog-backup.sh prune >>/root/graylog-nightly.log 2>&1' > graylog-backup, then try it once from /usr/local/sbin with sudo bash graylog-backup.sh; it should end with ok. To restore, run sudo bash graylog-restore.sh from that folder with the archive's full path. It confirms all four volumes are in the archive and stops before touching anything if a check fails, then reloads your filter and starts Graylog. To undo it, restore the archive it saved first. On a fresh server, do the install, inputs and backup steps, then restore. UDP logs sent during the pause are lost. Copy archives elsewhere with rsync -a, without --delete, and switch to Graylog's snapshot method once copies take too long.
Licence, updates and when one box is not enough
Graylog's licence is the SSPL (Server Side Public License) v1, as the LICENSE file in its graylog2-server repository and the docker-compose README both state; the example files and the image build files carry Apache 2.0. This is how we read it, not legal advice. The SSPL is copyleft, so if you give copies to anyone, changed or unchanged, you must give them the source as well. Its section 13 adds that if you make what Graylog does available to others as a service, you must publish the source of everything used to run that service, from monitoring and management tools to the backup and hosting software. Collecting your own company's logs is the normal case; selling hosted Graylog is a question for a lawyer.
Updates within 7.2 arrive through the image tags: make a fresh backup, run cd /opt/graylog && docker compose pull, then start the new images with docker compose up -d. For a bigger jump, read that version's upgrade notes first; Graylog 7.0, for instance, needed MongoDB 7.0 or newer. For redundancy Graylog describes multi-server layouts. Pair logs with Prometheus and Grafana for metrics or Zabbix for monitoring, or build a standalone search engine with self-hosted OpenSearch.
Questions people ask
- Will one server really do for Graylog?
- For a small team, yes. Graylog's own example Compose file puts all three parts on a single host. Its smallest production design uses two servers, so give your one machine the combined size and keep in mind there is no standby if it goes down.
- I have an old setup on Elasticsearch. Can I keep it?
- For now, but plan to move. Graylog 7.0 deprecated Elasticsearch and OpenSearch 1.x, and both are scheduled for removal in Graylog 8.0. Graylog points you to the Data Node, or to an OpenSearch version inside its compatibility range.
- Can my team reach Graylog at a normal web address?
- Yes, through a reverse proxy on the server that listens on 443 and forwards to 127.0.0.1:9000, and sets the X-Graylog-Server-URL header to your public address, as in Graylog's proxy examples. Let ports 80 and 443 through ufw ahead of the certificate request, and only after setup is finished and your admin password is in place.
- My switches can only send syslog to port 514. What then?
- Leave the input on 5140 and change the port mapping in the Compose file to 514:5140/udp, so the server answers on 514. Change 5140 to 514 in the filter script as well, because the filter looks at the port your devices sent to. Then reload both with
systemctl restart graylog-inputsandcd /opt/graylog && docker compose up -d. - Does Graylog cost anything to run?
- The free edition, Graylog Open, ships as the graylog-server package and the graylog/graylog image under the SSPL, with no licence fee for running it yourself. The other editions, Enterprise and Security, both install from the graylog-enterprise package and run on the graylog/graylog-enterprise image.