Deployment guide

Self-Host Neo4j on a VPS: Graph Database, Cypher and GraphRAG

Deploy on a VPS Cloud →

Tutorial

Self-hosting

Self-Host Neo4j on a VPS: Graph Database, Cypher and GraphRAG

Some data does not fit well in tables: who knows whom, which service depends on which, which document cites which contract. In SQL, every level of relationship costs a join. Neo4j, the most widely used graph database, stores nodes and their relationships directly and queries them in Cypher. This guide installs Neo4j Community Edition on a VPS with Docker Compose, publishes Neo4j Browser and the Bolt connection over HTTPS on a single name, then connects an application and lays the groundwork for GraphRAG.

7 min read3 steps
Contents· What Neo4j does1/12
  1. 01What Neo4j does
  2. 02What Neo4j brings
  3. 03Neo4j compared with other options
  4. 04Prerequisites
  5. 05Install Neo4j with Docker Compose
  6. 06Never publish 7474 or 7687 to the Internet
  7. 07Put Neo4j behind nginx over HTTPS
  8. 08First Cypher queries
  9. 09Connect an application
  10. 10GraphRAG: give an AI assistant connected facts
  11. 11Back up and update
  12. 12One-click install on ServOrbit

What Neo4j does

Neo4j records nodes — a customer, a product, a server — carrying labels (:Person, :Product) and properties, connected by directed, named relationships (:KNOWS, :BOUGHT, :DEPENDS_ON). A relationship is stored as a direct link between two nodes: following it needs no index lookup, however large the database grows.

You query it in Cypher, a language that draws the pattern you are looking for: (a:Person)-[:KNOWS]->(b:Person). Variable-length paths (-[:KNOWS*1..3]->), shortest paths and aggregations fit on one line, where SQL stacks joins or recursive queries.

What Neo4j brings

  • Cypher — MATCH, MERGE, variable-length and shortest paths, in a readable language.
  • Neo4j Browser — a web interface that runs queries and draws the resulting graph.
  • HTTP API — one Cypher query per POST call, JSON response, from any language.
  • Official Bolt drivers — Python, JavaScript, Java, Go and .NET.
  • Indexes and constraints — uniqueness, full-text search and vector indexes for embeddings.
  • ACID transactions — a consistent graph, even when several applications write to it.

Neo4j compared with other options

Scroll the table

Neo4jPostgreSQLQdrant
ModelNodes and relationshipsTables and joinsVectors and metadata
QueriesCypherSQLSimilarity search
StrengthMulti-level relationshipsTransactions and tabular dataSemantic search
Role in a RAGConnected, sourced factsBusiness dataPassages close to the question
CostVPS priceVPS priceVPS price

Prerequisites

An Ubuntu 24.04 VPS with 2 GB of RAM and 20 GB of disk, Docker and the Compose plugin installed, nginx and Certbot on the host, and a subdomain pointing to the VPS, for example neo4j.example.com. Neo4j is fastest when the graph fits in its page cache: plan more memory for a graph of tens of millions of nodes.

Install Neo4j with Docker Compose

  1. Prepare the directory and the password

    The .env file holds the password of the neo4j user. The backups folder will receive the backups: it belongs to user 7474, the container's user.

    mkdir -p /opt/neo4j/backups && cd /opt/neo4j
    echo "NEO4J_PASSWORD=$(openssl rand -hex 16)" > .env
    chmod 600 .env
    chown 7474:7474 backups
  2. Write the docker-compose.yml file

    Both ports are published on the loopback only: 7474 for Neo4j Browser and the HTTP API, 7687 for Bolt. NEO4J_AUTH creates the neo4j user with your password on first start, and the advertised Bolt address is the one nginx will publish over HTTPS.

    services:
      neo4j:
        image: neo4j:2026.10.2-community
        restart: unless-stopped
        ports:
          - "127.0.0.1:7474:7474"
          - "127.0.0.1:7687:7687"
        environment:
          NEO4J_AUTH: neo4j/${NEO4J_PASSWORD}
          NEO4J_server_bolt_advertised__address: neo4j.example.com:443
          NEO4J_server_memory_heap_initial__size: 512m
          NEO4J_server_memory_heap_max__size: 512m
          NEO4J_server_memory_pagecache_size: 512m
        volumes:
          - neo4j-data:/data
          - neo4j-logs:/logs
    volumes:
      neo4j-data: {}
      neo4j-logs: {}
  3. Start Neo4j

    The first start takes about thirty seconds; the root then answers with a small JSON document giving the version and the connection addresses.

    docker compose up -d
    docker compose ps
    curl -s http://127.0.0.1:7474/

Never publish 7474 or 7687 to the Internet

Keep both ports on 127.0.0.1: a database reachable directly from the Internet is scanned within hours. nginx carries HTTPS on 443, and the browser's Bolt connection goes through the same name over WebSocket. Keep NEO4J_AUTH too: without it, the neo4j user starts with the factory password neo4j, which everybody knows.

Put Neo4j behind nginx over HTTPS

Neo4j Browser needs two things: the HTTP interface and a Bolt connection, which the browser opens over WebSocket. nginx routes both on the same name: ordinary requests to 7474, WebSocket requests to 7687.

map $http_upgrade $neo4j_backend {
    default   127.0.0.1:7474;
    websocket 127.0.0.1:7687;
}
server {
    listen 80;
    server_name neo4j.example.com;
    location / {
        proxy_pass http://$neo4j_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}
certbot --nginx -d neo4j.example.com

Then open https://neo4j.example.com. In Neo4j Browser, enter neo4j+s://neo4j.example.com:443 as the Connect URL, the user neo4j and your password.

First Cypher queries

In Neo4j Browser, create a uniqueness constraint, a few nodes and their relationships, then follow a path of at most two levels. MERGE only creates a node or relationship if it does not exist yet: the query can be replayed without duplicates.

CREATE CONSTRAINT person_name IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE;
MERGE (sara:Person {name: 'Sara'})
MERGE (yassine:Person {name: 'Yassine'})
MERGE (casa:City {name: 'Casablanca'})
MERGE (sara)-[:KNOWS]->(yassine)
MERGE (yassine)-[:LIVES_IN]->(casa);
MATCH (p:Person {name: 'Sara'})-[:KNOWS*1..2]->(f)-[:LIVES_IN]->(c:City)
RETURN f.name, c.name;

The Browser draws the result; the :guide command opens its built-in tutorials, and :sysinfo shows the database size and cache usage.

Connect an application

From any language, the HTTP API takes a Cypher query by POST and answers in JSON, with HTTP basic authentication. An application installed on the same VPS rather uses an official driver over Bolt, at bolt://127.0.0.1:7687. From your development machine, an SSH tunnel makes the same port reachable without opening it to the Internet.

curl -s -u neo4j:$NEO4J_PASSWORD \
  -H 'Content-Type: application/json' \
  -d '{"statement": "MATCH (p:Person) RETURN p.name LIMIT 5"}' \
  https://neo4j.example.com/db/neo4j/query/v2
from neo4j import GraphDatabase

driver = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "<mot-de-passe>"))
records, _, _ = driver.execute_query("MATCH (p:Person) RETURN p.name AS name")
print([r["name"] for r in records])
ssh -L 7687:127.0.0.1:7687 [email protected]

Use the bolt:// scheme rather than neo4j:// for a local connection: the latter requests the routing table, which advertises the public address on port 443.

GraphRAG: give an AI assistant connected facts

A classic RAG retrieves passages close to the question. GraphRAG adds the relationships: entities extracted from documents (people, contracts, products) become nodes, linked to each other and to the passages that mention them. Neo4j holds both in the same database, thanks to its vector indexes.

CREATE VECTOR INDEX chunk_embedding IF NOT EXISTS
FOR (c:Chunk) ON c.embedding
OPTIONS {indexConfig: {`vector.dimensions`: 768, `vector.similarity_function`: 'cosine'}};

Vector search finds the relevant passages, then a Cypher query follows their relationships to gather context: the assistant answers with connected facts and the source of each one. Embeddings can come from a local model served by Ollama on the same VPS.

Back up and update

In Community Edition, backups are taken with the database stopped, using neo4j-admin database dump: a few seconds of downtime for a medium-sized graph. Schedule it at night.

cd /opt/neo4j
docker compose stop neo4j
docker compose run --rm -v /opt/neo4j/backups:/backups neo4j \
  neo4j-admin database dump neo4j --to-path=/backups --overwrite-destination=true
docker compose start neo4j

Then copy backups/neo4j.dump off the VPS. To update, change the image version in docker-compose.yml, then docker compose pull && docker compose up -d, right after a backup; read the release notes before moving to a new major version.

One-click install on ServOrbit

From the Marketplace in your client area, Neo4j installs on a ServOrbit VPS without writing a line: Neo4j Community Edition is deployed behind a small proxy that serves Neo4j Browser, the HTTP API and the Bolt connection on a single port, the password of the neo4j user is generated and shown in your client area, and everything is published over HTTPS on your domain or on the VPS's free subdomain. The Bolt port stays available locally, at bolt://127.0.0.1:7687, for your applications.

Share this articleservorbit.com/ma-en/blog/self-host-neo4j-vps-graph-database