Console Login

Self-Hosted APM: A GDPR-Safe Architecture for High-Performance Tracing

The "It Works on My Machine" Era is Dead. Here is How to Fix Production.

If you are still relying on tail -f /var/log/nginx/error.log to debug why your checkout process is timing out, you are flying blind. In the microservices architecture we see dominating 2021, a single user request might touch a load balancer, three Node.js services, a Redis cache, and a MySQL backend. When that request hangs for 2 seconds, where is the bottleneck? Without Application Performance Monitoring (APM), you are just guessing.

But here is the catch for us in Europe, and specifically in Norway: Data Sovereignty.

Since the Schrems II ruling last year invalidated the Privacy Shield, sending your trace data—which often inadvertently contains PII like IPs or user IDs—to a US-based SaaS provider is a legal minefield. The Datatilsynet (Norwegian Data Protection Authority) has been clear: you own your compliance risk. The pragmatic CTO doesn't offload this risk; they architect around it.

The solution is self-hosted observability. In this guide, I will show you how to deploy the Elastic APM stack (v7.12) on a CoolVDS instance. We chose this stack because it offers distributed tracing that rivals Datadog or New Relic, but keeps 100% of the data on your own encrypted disks in Oslo.

1. The Hardware Reality Check: Why Elastic Needs NVMe

Before we touch a config file, we need to talk about I/O. Elasticsearch (the storage engine behind Elastic APM) is notoriously I/O heavy. It relies on massive amounts of random writes for indexing and random reads for aggregation.

I have seen DevOps teams try to save €20 by running an ELK (Elasticsearch, Logstash, Kibana) stack on cheap VPS providers using shared HDD or SATA SSD storage. The result is always the same: indexing pressure chokes the CPU, the cluster goes yellow, and you lose the very traces you need to debug an outage.

Pro Tip: Never run Elasticsearch on a host with "noisy neighbor" issues. We use KVM virtualization on CoolVDS specifically to ensure that your allocated I/O operations are yours alone. For a production APM stack handling >500 transactions per second, standard SSDs won't cut it. You need the NVMe instances we provide to keep up with Lucene merging segments in the background.

2. Preparing the Host Environment

We will assume you are running Ubuntu 20.04 LTS. First, Elasticsearch requires a specific kernel tweak. By default, the operating system limits the number of memory map areas a process can have, which will cause Elasticsearch to crash immediately upon startup.

Apply this persistent fix:

# Check current limit (usually 65530)
cat /proc/sys/vm/max_map_count

# Increase the limit permanently
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

# Apply without reboot
sudo sysctl -p

Next, ensure your Docker environment is clean. We are using Docker Compose because it simplifies the networking between the APM Server, Elasticsearch, and Kibana.

3. The Deployment Manifest

Create a directory /opt/apm-stack and create a docker-compose.yml file. We are locking versions to 7.12.0 to ensure stability.

version: '2.2'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
    container_name: elasticsearch
    environment:
      - node.name=es01
      - cluster.name=coolvds-apm-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
      - discovery.type=single-node
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic

  kibana:
    image: docker.elastic.co/kibana/kibana:7.12.0
    container_name: kibana
    ports:
      - 5601:5601
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
      ELASTICSEARCH_HOSTS: '["http://elasticsearch:9200"]'
    networks:
      - elastic
    depends_on:
      - elasticsearch

  apm-server:
    image: docker.elastic.co/apm/apm-server:7.12.0
    container_name: apm-server
    command: >
      apm-server -e
        -E apm-server.rum.enabled=true
        -E setup.kibana.host=kibana:5601
        -E setup.template.settings.index.number_of_shards=1
        -E apm-server.kibana.enabled=true
        -E apm-server.kibana.host=kibana:5601
        -E output.elasticsearch.hosts=["elasticsearch:9200"]
    ports:
      - 8200:8200
    networks:
      - elastic
    depends_on:
      - elasticsearch
      - kibana

volumes:
  esdata:
    driver: local

networks:
  elastic:
    driver: bridge

Critical Architecture Note: Look at the ES_JAVA_OPTS line. We are allocating 2GB of heap to Elasticsearch. On a production CoolVDS server, you should follow the "50% rule": allocate 50% of available RAM to the heap, but never cross 32GB (to avoid Zero-Based Compressed Oops issues). If you rent our 8GB RAM plan, set this to 4GB.

4. Instrumenting Your Application

Having the server running is useless without data. Let's say you have a Node.js Express application. You don't need to rewrite your code; you just need to inject the agent before any other module is required.

First, install the agent:

npm install elastic-apm-node --save

Then, at the very top of your index.js or app.js:

// This must be the very first line!
const apm = require('elastic-apm-node').start({
  serviceName: 'coolvds-checkout-service',
  serverUrl: 'http://YOUR_COOLVDS_IP:8200',
  environment: 'production',
  captureBody: 'all' // Be careful with GDPR here!
})

const express = require('express')
const app = express()
// ... rest of your application

Security Warning

In the config above, captureBody: 'all' is great for debugging but dangerous for compliance. If a user submits a form with their credit card or social security number, that data ends up in your logs. I recommend setting this to 'errors' or using transaction filters to scrub sensitive fields before they leave the application.

5. Securing the Dashboard

By default, Kibana on port 5601 is open to the world. Do not leave this exposed. In 2021, scanning bots will find an open Kibana instance in under 15 minutes.

The best practice is to block port 5601 via UFW and use an Nginx reverse proxy with Basic Auth. Here is a battle-tested Nginx block:

server {
    listen 80;
    server_name monitor.yourdomain.no;

    location / {
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
        
        proxy_pass http://localhost:5601;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Why This Matters for Norway

Latency is not just technical; it's geographical. If your users are in Oslo and your APM server is in Virginia (us-east-1), you are introducing network lag just to monitor network lag. By hosting your APM stack on a CoolVDS server in our local datacenter, you achieve near-zero latency ingestion.

Furthermore, when the legal department asks, "Where is our data?", you can point to a specific server rack, not a nebulous cloud region. In the post-Schrems II landscape, that certainty is worth its weight in gold.

Ready to take control of your infrastructure metrics? Don't let slow I/O kill your observability efforts. Deploy a high-frequency NVMe VPS with CoolVDS today and start tracing in under 60 seconds.