AI Automation

AI log analysis and anomaly detection on a Linux VPS

You have a Linux VPS, a handful of services, and log files growing by the hour. Syslog, Nginx access logs, auth failures, kernel messages. You know something is probably wrong in there, but grepping through megabytes of text is not a sustainable way to find it. This is where AI log analysis and anomaly detection come in. I will show you a working setup that runs on a single Linux VPS without needing a GPU or a data science degree, using tools that are current and maintained in 2026.

Key takeaways

  • AI log analysis on a VPS works best as a two-stage pipeline: fast statistical baselining first, then LLM-based interpretation of what the anomalies actually mean.
  • You do not need a GPU. A small embedding model plus a rules engine handles 80% of real detections on a 2-4 GB VPS.
  • Collect, normalize, then detect. Garbage in, garbage out applies to logs more than almost anything else.
  • Alerts must be actionable. A raw anomaly list is noise; an interpreted one is a ticket.

Prerequisites

  • A Linux VPS running Ubuntu 24.04 LTS or Debian 12, with root or sudo access.
  • At least 2 GB of RAM for a minimal setup. 4 GB is comfortable for the LLM-assisted tier.
  • Basic familiarity with systemd, journalctl, and Docker Compose.

Why traditional log monitoring is not enough

Tools like fail2ban and simple grep patterns have been around for decades, and they still work for known signatures. An attacker brute-forcing SSH matches a pattern. A service crashing matches a repeated error string. But the hard part of log analysis is not the known stuff. It is the stuff you have never seen before: a sudden jump in 401 responses from one IP, a gradual slowdown in your PostgreSQL query times, a user agent string that shifts over three weeks before an attack.

These are anomalies, not signatures. They do not look like anything you could have written a rule for. That is the gap AI log analysis fills. Instead of asking "does this line match a known bad pattern?", it asks "does this behavior differ from what this server normally does?". That is a different problem, and it needs a different toolchain.

The two-tier pipeline for AI log analysis

Do not try to feed raw logs directly into a large language model. It is expensive, slow, and the results are worse than a simple statistical check. The pattern that actually works on a VPS has two tiers, and the order matters.

  1. Statistical tier: parse logs into structured fields, then build baselines per field. Count events per minute per source IP, per status code, per service. This catches volume anomalies instantly.
  2. Interpretation tier: take the statistically flagged events and pass them to a local LLM to summarize, correlate, and classify what is happening. This turns "count of 500 errors from 203.0.113.7 up 400%" into "possible scanner probing /api/v2, recommend blocking the IP".

The statistical tier is cheap. It is a few hundred lines of Python or Go and it runs in a few MB of RAM. The interpretation tier is where you spend your compute budget, and you only invoke it on the small subset of logs that actually look abnormal. This is the architecture that keeps AI log analysis viable on a VPS with 2 GB of RAM instead of a bare-metal cluster.

Step 1: Collect and normalize logs

Everything downstream depends on the quality of this step. Start with systemd-journald because it is already on every modern distro, then add your application logs.

# Verify journald is running and check disk usage
systemctl status systemd-journald
journalctl --disk-usage

# Keep journald from eating your disk
sudo nano /etc/systemd/journald.conf

Set SystemMaxUse=500M and MaxRetentionSec=7day in that file, then restart journald. Log rotation is a separate concern from AI log analysis, but a pipeline fed by unbounded logs is a pipeline that dies at 3 AM. If you need the full rotation story, I have written about configuring logrotate on a VPS separately.

For Nginx and other text-file logs, you want a shipper that parses them into JSON. Vector is my choice here because it is fast, written in Rust, and light on memory.

# Install Vector on Debian/Ubuntu
curl -1sLf 'https://repositories.timber.io/public/vector/cfg/setup/bash.deb.sh' | sudo -E bash
sudo apt install vector -y

Here is a minimal Vector config that reads Nginx logs, adds a hostname tag, and ships them to your analysis backend as JSON:

# /etc/vector/vector.yaml
sources:
  nginx_main:
    type: file
    include: ["/var/log/nginx/access.log"]
    read_from: beginning
  journald_sys:
    type: journald
    include_units: ["ssh.service", "nginx.service"]

transforms:
  parse_nginx:
    type: remap
    inputs: ["nginx_main"]
    source: |
      . = parse_regex!(.message, r'^(?P<ip>\S+) - - \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) (?P<proto>[^"]+)" (?P<status>\d+) (?P<bytes>\d+)')

sinks:
  loki_out:
    type: loki
    inputs: ["parse_nginx", "journald_sys"]
    endpoint: "http://localhost:3100"
    encoding:
      codec: json

Verify your pipeline:

sudo vector validate --config-yaml /etc/vector/vector.yaml
sudo systemctl restart vector
sudo systemctl status vector

You should see active (running), and after a minute of traffic you can query Loki to confirm documents are flowing. This is the moment to get right. A broken collection step means your AI log analysis is analyzing an empty room.

Step 2: Store and baseline with Loki and a rules engine

Loki is the right store for this job. It is designed for log aggregation, and it indexes labels, not content, which keeps memory usage reasonable. Run it in Docker Compose with the detection engine from the same stack.

# docker-compose.yml snippet for Loki and a simple anomaly engine
services:
  loki:
    image: grafana/loki:3.4
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml

  anomaly-engine:
    build: ./engine
    environment:
      LOKI_URL: "http://loki:3100"
      INTERVAL_SECONDS: "60"
    depends_on:
      - loki

The anomaly engine is a small Python service that queries Loki every 60 seconds, groups the last minute of events by label and status code, and compares the counts to a rolling baseline. Any metric that deviates beyond a threshold you set gets emitted as a candidate anomaly. I use the scipy z-score for this, but a simple median plus median absolute deviation (MAD) works just as well and is more robust to spikes.

# engine/detector.py (simplified)
import requests, statistics

def fetch_counts(since):
    q = f'count_over_time({{job="vector"}}[1m])'
    r = requests.get("http://loki:3100/loki/api/v1/query",
                     params={"query": q}).json()
    return r["data"]["result"]

def is_anomaly(value, history):
    med = statistics.median(history)
    mad = statistics.median([abs(v - med) for v in history])
    if mad == 0: return False
    z = 0.6745 * (value - med) / mad
    return abs(z) > 3.5

Verify the engine is writing findings:

curl -s http://localhost:3100/loki/api/v1/query \
  --data-urlencode 'query=count_over_time({job="vector"}[1m])' | head -20

You should see JSON with your series names and values. The z-score check is what converts raw counts into anomaly candidates, and this is the workhorse of your AI log analysis stack. Test it by deliberately hitting a non-existent endpoint a hundred times in a minute, then confirm your engine flags it.

Step 3: Interpret anomalies with a local LLM

The statistical tier flagged something. Now you need to know what it means. This is where a local LLM earns its place. On a 4 GB VPS, a quantized 3-4 billion parameter model runs acceptably for short summarization tasks. Ollama is the easiest way to run one, and I already covered the mechanics in self-hosting an LLM API with Ollama.

# Install Ollama and pull a compact model
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:3b-instruct-q4_K_M

The interpretation service fetches candidate anomalies from your engine, formats the relevant log lines into a prompt, and asks the model for a verdict. Keep the context tight. You are not asking it to read your whole log, you are asking it to classify a specific incident.

# engine/interpreter.py (simplified)
import ollama

def interpret(anomaly):
    context = format_context(anomaly["log_lines"])
    prompt = f"""Classify this log anomaly. Give a one-line summary and a suggested action.
{context}
Answer as: SEVERITY | SUMMARY | ACTION"""
    resp = ollama.generate(model="qwen2.5:3b-instruct-q4_K_M",
                           prompt=prompt)
    return resp["response"]

Verify the model responds:

ollama run qwen2.5:3b-instruct-q4_K_M "Summarize: SSH failed password for root from 203.0.113.7 50 times in 60s"

Expect an answer that identifies a brute-force attempt and suggests blocking the IP. That single capability, turning a statistical flag into a readable verdict, is the whole point of the interpretation tier. Without it, you have a list of numbers. With it, you have a triage queue.

Step 4: Alert on what matters

An unread alert is the same as no alert. The failure mode of most monitoring setups is alert fatigue, so be deliberate about what crosses the threshold into your pager. I only alert on two things: anomalies the LLM classifies as HIGH or CRITICAL, and anomalies that recur three times in ten minutes. Everything else goes into a daily digest.

For the urgent path, a simple webhook to a notification channel works. Alertmanager is the standard choice if you want routing and deduplication, and I have covered a Prometheus Alertmanager pipeline before. For a single VPS, a Python script that fires a message when the interpreter returns a high-severity verdict is enough.

# engine/alerter.py (simplified)
import requests

def alert(verdict, anomaly):
    msg = {"text": f"[{verdict['severity']}] {verdict['summary']} | Action: {verdict['action']}"}
    requests.post(os.environ["WEBHOOK_URL"], json=msg)

Verify the alert path: trigger a deliberate surge of 404s, confirm the LLM flags it as a scan, and check that your webhook fired. A quiet test is not a test. If you want the positive side of this story, the same pipeline catches application bugs before users do. A sudden rise in 500s from one code path is an anomaly, and the LLM will describe it as one, which turns your monitoring from a security tool into a reliability tool.

What this costs on a real VPS

Let me be honest about resource usage, because the marketing around AI tends to ignore it. On a 2 GB VPS, the statistical tier and Loki run comfortably. The LLM tier does not. Running a 3B parameter model needs about 2.5 GB of RAM just for the weights, plus overhead for the context window. That is a 4 GB VPS, minimum, and you will feel the memory pressure.

The practical split is this. On a 2 GB VPS, run the statistical tier and use the LLM as a batch job you invoke manually or once a day. On a 4 GB or 8 GB VPS, run the LLM continuously and get real-time interpretation. If you are shopping for hardware, this is the kind of workload where the difference between a 2 GB and a 4 GB VPS pricing tier determines what architecture you can run at all.

The alternative is to offload interpretation to an external API, but that means shipping log excerpts off your server. For security logs, I prefer keeping everything local. A Windows VPS can run the same stack if you prefer the ecosystem, but the Linux tooling for this specific problem is more mature, and the memory overhead of Windows makes the 4 GB requirement tighter.

Troubleshooting a broken AI log analysis pipeline

Three failures account for most of what goes wrong with this setup.

No data in Loki. The Vector config has a regex that does not match your log format, so every line is dropped. Check this first:

sudo vector tap --config-yaml /etc/vector/vector.yaml | head -20

If nothing appears, your regex is wrong. The Nginx format varies by distro, and my example assumes the default combined format. Adjust the pattern to your actual log line, or use parse_regex! with a known-good test string.

The LLM is too slow or OOMs. A 3B model on 4 GB of RAM is near the limit. Reduce the context you send, force the model to answer in a fixed format, and use OLLAMA_NUM_PARALLEL=1 to prevent concurrent generations from exhausting memory. If it still dies, drop to a 1B parameter model for the interpretation tier. You lose nuance but gain stability.

False positives drowning you. Raise the z-score threshold, or require the anomaly to persist for two consecutive intervals. Most false positives are single-minute blips, and they go away when you demand persistence.

FAQ

Do I need a GPU for AI log analysis on a VPS?

No. The statistical tier is pure CPU and trivial. The interpretative tier runs on CPU with a quantized model, just more slowly. A 3B model answers a short classification prompt in 2-5 seconds on a modern vCPU, which is fine for triage. A GPU only matters if you want real-time analysis of every single log line, which is unnecessary.

How much log data can this handle?

Loki handles several gigabytes per day on a VPS with reasonable retention. The bottleneck is the LLM tier, which only processes flagged anomalies, so your capacity scales with the quality of your statistical baseline, not your log volume. A typical small VPS generating 500 MB of logs a day is an easy workload.

Can I use this for Windows event logs?

Yes, but you need a shipper. Vector has a windows_eventlog source that reads the Event Log, and the rest of the pipeline is identical. The memory math changes, since Windows Server itself needs more RAM, so plan for 6-8 GB if you want the LLM tier running alongside.

What is the weakest part of this setup?

The statistical baselining is the weak link. It is simple by design, so it misses slow, distributed attacks that never spike on a single metric. Correlating across multiple signals, like failed auths across all your user accounts, needs a more advanced model. For a single VPS, the simple version catches most real incidents, and you can accept the gap.

How do I keep the AI component up to date?

Ollama makes model updates one command: ollama pull qwen2.5:3b-instruct-q4_K_M. Vector and Loki update through your package manager or Docker image tags. The detection engine is your own code, so version it in git. The pipeline has no moving parts that break silently, which is the real advantage of keeping it simple.

Related articles

AI日志分析与异常检测实践要点

在VPS上做AI日志分析,先用统计方法做基线检测,再用本地大模型解释异常,这个两级架构最实用。数据收集和规范化是最关键的步骤,日志格式不对,后面全白搭。小规模部署不需要GPU,4GB内存的VPS就能跑通完整链路,2GB的可以只跑统计层。告警要聚焦在模型判定为高危的事件上,避免告警疲劳。

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.