Threat Intelligence
    MariaDB + PHP

    Deploy MISP on a VPS

    Production-grade MISP threat intelligence platform on a RamNode VPS — Ubuntu 24.04 install, MariaDB tuning, workers, hardening, and operational guidance.

    MISP (Malware Information Sharing Platform) is the de facto open-source standard for sharing threat intelligence indicators between organizations. It is what SOC teams, CERTs, and ISACs use to operationalize IOCs, attack patterns, and adversary tradecraft. This guide walks through a production deployment on a RamNode KVM VPS, including database tuning, worker configuration, hardening, and the operational concerns that the upstream installer skips over.

    Sizing and Prerequisites

    MISP is heavier than its reputation suggests. The web tier is PHP under FPM, the database is MariaDB, and there is a Redis-backed background worker pool that you cannot skip in production. Plan for the following minimums on a RamNode plan:

    • 4 GB RAM minimum, 8 GB recommended once you start enabling feeds and correlation
    • 4 vCPU minimum if you run correlation on imports
    • 60 GB disk, ideally NVMe, since the database grows quickly with feed ingestion
    • Ubuntu 24.04 LTS (24.04 is the cleanest path for the current installer)

    RamNode does not allow mail services on their VPS, so the email notification features in MISP need to be configured against an external SMTP relay (SES, Postmark, Mailgun, or your own off-net mail server). Skip the local Postfix configuration steps that the upstream MISP docs include.

    Initial Server Hardening

    Start with a clean Ubuntu 24.04 install. Update and install the baseline tools:

    shell
    apt update && apt -y full-upgrade
    apt -y install ufw fail2ban unattended-upgrades curl gnupg2 software-properties-common

    Configure unattended security upgrades:

    shell
    dpkg-reconfigure -plow unattended-upgrades

    Lock down SSH. Edit /etc/ssh/sshd_config.d/99-hardening.conf:

    shell
    PermitRootLogin prohibit-password
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    AllowUsers mispadmin
    MaxAuthTries 3
    LoginGraceTime 30

    Restart SSH and confirm key-based access works before closing the existing session:

    shell
    systemctl restart ssh

    Configure UFW with a deny-by-default posture. We will open only what MISP needs:

    shell
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow 22/tcp
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw --force enable

    Installing MISP

    The upstream INSTALL.ubuntu2404.sh script is the supported path. Do not run it as root, do not run it interactively from a flaky SSH session, and do read it before executing. Pin to a tagged commit if you are deploying multiple servers from this base.

    Create an unprivileged operator account first:

    shell
    adduser mispadmin
    usermod -aG sudo mispadmin
    rsync -a --chown=mispadmin:mispadmin /root/.ssh /home/mispadmin/

    Switch to that user, then pull and run the installer:

    shell
    su - mispadmin
    wget --no-cache -O /tmp/INSTALL.sh https://raw.githubusercontent.com/MISP/MISP/2.5/INSTALL/INSTALL.ubuntu2404.sh
    chmod +x /tmp/INSTALL.sh
    sudo /tmp/INSTALL.sh -A

    The -A flag runs the full default install: MariaDB, Apache, PHP 8.3, MISP core, MISP modules, and the background workers. Expect this to take 20 to 45 minutes depending on disk and network. The installer prints credentials at the end. Capture them into your password manager immediately and do not leave them in the shell history.

    MariaDB Tuning for MISP Workloads

    The default MariaDB config is sized for a generic LAMP host, not for MISP correlation. Edit /etc/mysql/mariadb.conf.d/99-misp.cnf with values that suit your plan size. The example below targets an 8 GB RamNode VPS:

    shell
    [mysqld]
    innodb_buffer_pool_size = 4G
    innodb_buffer_pool_instances = 4
    innodb_log_file_size = 512M
    innodb_log_buffer_size = 32M
    innodb_flush_log_at_trx_commit = 2
    innodb_flush_method = O_DIRECT
    innodb_io_capacity = 2000
    innodb_io_capacity_max = 4000
    innodb_read_io_threads = 8
    innodb_write_io_threads = 8
    innodb_file_per_table = 1
    
    max_connections = 200
    table_open_cache = 4000
    tmp_table_size = 256M
    max_heap_table_size = 256M
    
    query_cache_type = 0
    query_cache_size = 0

    The buffer pool sizing rule is roughly 50 to 60 percent of RAM on a dedicated DB host, but since MISP shares the box with PHP-FPM and Redis, 4 GB out of 8 is the sane ceiling. On a 4 GB plan, drop this to 1.5 GB. Restart MariaDB and validate:

    shell
    systemctl restart mariadb
    mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

    TLS with Let's Encrypt

    Replace the installer's self-signed certificate with a real one. Assuming your DNS A record for misp.example.org already points at the VPS:

    shell
    apt install -y certbot python3-certbot-apache
    certbot --apache -d misp.example.org --redirect --hsts \
      --agree-tos --email admin@example.org --no-eff-email

    Force HTTP/2 and add HSTS preload-grade headers. Edit /etc/apache2/sites-available/misp-ssl.conf inside the <VirtualHost> block:

    shell
    Protocols h2 http/1.1
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    
    SSLProtocol -all +TLSv1.2 +TLSv1.3
    SSLHonorCipherOrder on
    SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305

    Reload Apache:

    shell
    apache2ctl configtest && systemctl reload apache2

    MISP Core Configuration

    Log into the web UI with the admin credentials from the installer output. The first task is to walk the diagnostics page at Administration > Server Settings > Diagnostics. Every red item is a real problem. Common items to fix on a fresh install:

    • MISP.baseurl should be your full HTTPS URL, no trailing slash
    • MISP.email set to a real mailbox that you control (notifications only, RamNode will block 25/tcp)
    • MISP.contact and MISP.org set to your team identifiers
    • Security.salt rotated if you cloned from another instance
    • GnuPG.email and signing key generated for event signing

    Generate a signing key:

    shell
    sudo -u www-data gpg --homedir /var/www/MISP/.gnupg --quick-gen-key "MISP Signing <admin@example.org>" rsa4096 sign 0
    sudo -u www-data gpg --homedir /var/www/MISP/.gnupg --export --armor admin@example.org > /var/www/MISP/app/webroot/gpg.asc
    chown www-data:www-data /var/www/MISP/app/webroot/gpg.asc

    In the UI, set GnuPG.binary to /usr/bin/gpg, GnuPG.homedir to /var/www/MISP/.gnupg, and the email to match the key.

    External SMTP Configuration

    Because RamNode blocks outbound mail traffic on standard ports for new accounts and prohibits running mail services on the VPS, MISP must relay through an external provider. In the server settings, set:

    • SMTP.host to your provider hostname (for example, email-smtp.us-east-1.amazonaws.com)
    • SMTP.port to 587
    • SMTP.username and SMTP.password to your provider credentials
    • SMTP.transport to smtp
    • SMTP.tls to true

    Send a test notification from the diagnostics page and confirm delivery.

    Background Workers

    MISP relies on five worker queues: default, prio, email, cache, and update. The installer sets these up under systemd. Confirm they are running:

    shell
    systemctl status misp-workers

    If you are running with constrained RAM, edit /etc/systemd/system/misp-workers.service to cap the number of workers per queue. The default of 5 is excessive on a 4 GB plan; 2 per queue is usually sufficient until you scale up.

    Monitor the queues from Administration > Jobs > Worker Status. Stuck workers are the single most common source of operational pain. Add this to your monitoring:

    shell
    sudo -u www-data /var/www/MISP/app/Console/cake Admin getWorkers

    Feed Configuration

    Feeds are where MISP earns its keep. Navigate to Sync Actions > List Feeds and enable the default CIRCL OSINT feed, the Botvrij feed, and any vendor or ISAC feeds you have access to. Set the caching schedule from the Administration > Scheduled Tasks page; pull and cache jobs should run on staggered intervals to avoid pinning the worker pool.

    For high-volume feeds, disable correlation on import until you have validated the source. Correlation against a million-event corpus on a 4 vCPU VPS will pin the database for hours.

    Backups

    MISP backups need to capture three things: the database, the GnuPG keyring, and the app/files/certs and app/Config directories. Drop this into /usr/local/sbin/misp-backup.sh:

    shell
    #!/usr/bin/env bash
    set -euo pipefail
    TS=$(date +%Y%m%d-%H%M%S)
    DEST=/var/backups/misp
    mkdir -p "$DEST"
    mysqldump --single-transaction --routines --triggers misp | gzip > "$DEST/misp-db-$TS.sql.gz"
    tar -czf "$DEST/misp-config-$TS.tar.gz" \
      /var/www/MISP/app/Config \
      /var/www/MISP/app/files/certs \
      /var/www/MISP/.gnupg
    find "$DEST" -type f -mtime +14 -delete

    Schedule it from root's crontab:

    shell
    30 2 * * * /usr/local/sbin/misp-backup.sh >> /var/log/misp-backup.log 2>&1

    Push the backup directory off-box with rsync or restic to object storage. A backup that lives on the same VPS as the database is not a backup.

    Operational Monitoring

    Watch the following metrics, either via your monitoring stack or via cron-driven alerts:

    • MariaDB connection count vs max_connections
    • Redis memory usage and eviction rate
    • Worker queue depth (alert if any queue is over 1000)
    • Disk usage on /var/lib/mysql and /var/www/MISP/app/files
    • Cert expiry on Let's Encrypt
    • MISP UI HTTP 200 response from an external prober

    Plausible Analytics or a self-hosted Uptime Kuma both work for the synthetic check.

    Once the platform is stable, the next round of work is integration: pushing IOCs into your SIEM, pulling events from MISP into a SOAR pipeline, and configuring sync to peer instances over the secure REST API. Each of those is a guide of its own, but a stable base instance with workers, backups, and TLS in good shape is the prerequisite for any of it.