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
POSTcall, 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
| Neo4j | PostgreSQL | Qdrant | |
|---|---|---|---|
| Model | Nodes and relationships | Tables and joins | Vectors and metadata |
| Queries | Cypher | SQL | Similarity search |
| Strength | Multi-level relationships | Transactions and tabular data | Semantic search |
| Role in a RAG | Connected, sourced facts | Business data | Passages close to the question |
| Cost | VPS price | VPS price | VPS 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
Prepare the directory and the password
The
.envfile holds the password of theneo4juser. Thebackupsfolder 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 backupsWrite 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_AUTHcreates theneo4juser 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: {}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.comThen 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/v2from 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 neo4jThen 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.
servorbit.com/ma-en/blog/self-host-neo4j-vps-graph-database