Neo4j is a graph database: it stores data as nodes connected by typed relationships, and you query it with Cypher, a pattern-matching language designed for traversing those connections. It is a good fit for recommendation engines, fraud detection, access control graphs and any data where the relationships matter as much as the records. In this tutorial you will install Neo4j Community Edition on Ubuntu 24.04 from the official repository, set the admin password, size its memory, model a small graph with Cypher, and back it up.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, such as a CubePath VPS, with at least 2 GB of RAM (4 GB or more is recommended).
- A non-root user with
sudoprivileges. - An SSH client on your local machine, used to reach Neo4j Browser through a tunnel.
Step 1 - Adding the Neo4j repository
Neo4j publishes Debian packages in its own APT repository. Download the signing key, convert it to the binary keyring format and store it in /etc/apt/keyrings:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/neotechnology.gpg
Add the repository, pinned to that key:
echo "deb [signed-by=/etc/apt/keyrings/neotechnology.gpg] https://debian.neo4j.com stable latest" | sudo tee /etc/apt/sources.list.d/neo4j.list
Refresh the package index and confirm that APT sees the package:
sudo apt update
apt-cache policy neo4j
The output should show a candidate version coming from https://debian.neo4j.com stable/latest.
Step 2 - Installing Neo4j
Current Neo4j releases (calendar versions such as 2025.x) require Java 21, which APT installs automatically as a dependency together with cypher-shell:
sudo apt install neo4j
Check the installed version:
neo4j --version
neo4j 2025.xx.x
The package uses these locations:
| Path | Contents |
|---|---|
/etc/neo4j/neo4j.conf | Main configuration file |
/var/lib/neo4j/data | Databases and transaction logs |
/var/lib/neo4j/plugins | Plugin JAR files |
/var/log/neo4j | Logs |
Step 3 - Setting the initial password
The built-in neo4j user must get a password before the first login. Setting it before the first start avoids the forced password change prompt. The password must be at least 8 characters long; replace your_strong_password:
sudo neo4j-admin dbms set-initial-password your_strong_password
Changed password for user 'neo4j'. IMPORTANT: this change will only take effect if performed before the database is started for the first time.
Step 4 - Configuring memory and network
Neo4j performance depends mostly on two memory areas: the JVM heap (query execution and transaction state) and the page cache (the graph data kept in RAM). Ask Neo4j for a recommendation based on the server's memory:
sudo neo4j-admin server memory-recommendation
server.memory.heap.initial_size=1g
server.memory.heap.max_size=1g
server.memory.pagecache.size=512m
...
Open the configuration file:
sudo nano /etc/neo4j/neo4j.conf
Find the memory settings, uncomment them and paste the recommended values. Setting the initial and maximum heap to the same value avoids pauses caused by heap resizing:
server.memory.heap.initial_size=1g
server.memory.heap.max_size=1g
server.memory.pagecache.size=512m
By default Neo4j only listens on localhost, which is the safest option: the HTTP interface (port 7474) and the Bolt driver protocol (port 7687) stay private, and you reach them through an SSH tunnel. Leave server.default_listen_address commented out unless an application on another server must connect. In that case, set it to listen on all interfaces:
server.default_listen_address=0.0.0.0
and allow Bolt only from that application server's IP, for example 203.0.113.20:
sudo ufw allow from 203.0.113.20 to any port 7687 proto tcp
WarningDo not expose ports 7474 and 7687 to the whole internet without TLS. Credentials travel in clear text over unencrypted Bolt and HTTP.
Step 5 - Starting Neo4j
Enable the service so it starts at boot, and start it now:
sudo systemctl enable --now neo4j
Check its status:
sudo systemctl status neo4j
● neo4j.service - Neo4j Graph Database
Loaded: loaded (/usr/lib/systemd/system/neo4j.service; enabled; preset: enabled)
Active: active (running) since ...
Startup takes a few seconds. Follow the log until you see Started.:
sudo journalctl -u neo4j -f
Then test a query with cypher-shell. It prompts for the password:
cypher-shell -u neo4j "RETURN 'connected' AS status;"
+-------------+
| status |
+-------------+
| "connected" |
+-------------+
Step 6 - Opening Neo4j Browser through an SSH tunnel
Neo4j Browser is a web UI served on port 7474 that talks to the database over Bolt on port 7687. From your local machine, forward both ports over SSH (replace your_user and your_server_ip):
ssh -L 7474:localhost:7474 -L 7687:localhost:7687 your_user@your_server_ip
Keep that session open and browse to http://localhost:7474. Connect with the URL neo4j://localhost:7687, user neo4j and the password from Step 3.
Step 7 - Modeling data with Cypher
Open an interactive shell on the server:
cypher-shell -u neo4j
Before loading data, create uniqueness constraints. They prevent duplicate nodes and also create an index that makes lookups by that property fast:
CREATE CONSTRAINT person_email IF NOT EXISTS FOR (p:Person) REQUIRE p.email IS UNIQUE;
CREATE CONSTRAINT product_sku IF NOT EXISTS FOR (p:Product) REQUIRE p.sku IS UNIQUE;
Load a small purchase graph. MERGE creates a node or relationship only if it does not exist yet, so you can run the statement repeatedly without duplicates:
MERGE (alice:Person {email: '[email protected]'}) SET alice.name = 'Alice'
MERGE (bob:Person {email: '[email protected]'}) SET bob.name = 'Bob'
MERGE (carol:Person {email: '[email protected]'}) SET carol.name = 'Carol'
MERGE (laptop:Product {sku: 'LAP-01'}) SET laptop.name = 'Laptop'
MERGE (mouse:Product {sku: 'MOU-01'}) SET mouse.name = 'Mouse'
MERGE (dock:Product {sku: 'DCK-01'}) SET dock.name = 'Docking station'
MERGE (alice)-[:KNOWS]->(bob)
MERGE (alice)-[:BOUGHT]->(laptop)
MERGE (bob)-[:BOUGHT]->(laptop)
MERGE (bob)-[:BOUGHT]->(mouse)
MERGE (carol)-[:BOUGHT]->(laptop)
MERGE (carol)-[:BOUGHT]->(dock);
0 rows
ready to start consuming query after 45 ms, results consumed after another 0 ms
Added 6 nodes, Set 12 properties, Created 6 relationships, Added 6 labels
Query a pattern: who bought the laptop?
MATCH (p:Person)-[:BOUGHT]->(:Product {sku: 'LAP-01'})
RETURN p.name ORDER BY p.name;
+---------+
| p.name |
+---------+
| "Alice" |
| "Bob" |
| "Carol" |
+---------+
Now a query that is awkward in SQL but natural in a graph: recommend products to Alice based on what other buyers of her products also bought, ranked by how many of them bought it:
MATCH (alice:Person {email: '[email protected]'})-[:BOUGHT]->(:Product)<-[:BOUGHT]-(other:Person)-[:BOUGHT]->(rec:Product)
WHERE NOT EXISTS { (alice)-[:BOUGHT]->(rec) }
RETURN rec.name AS recommendation, count(DISTINCT other) AS score
ORDER BY score DESC;
+---------------------------+
| recommendation | score |
+---------------------------+
| "Mouse" | 1 |
| "Docking station" | 1 |
+---------------------------+
Use PROFILE in front of a query to see whether it uses an index. A lookup on email should start with a NodeUniqueIndexSeek operator instead of a full NodeByLabelScan:
PROFILE MATCH (p:Person {email: '[email protected]'}) RETURN p.name;
List the indexes and constraints you created, then leave the shell:
SHOW INDEXES;
SHOW CONSTRAINTS;
:exit
Step 8 - Enabling the APOC plugin (optional)
APOC Core adds hundreds of utility procedures and functions (data conversion, batch operations, path expansion). The package ships the JAR in the labs directory; copy it to plugins:
ls /var/lib/neo4j/labs/
sudo cp /var/lib/neo4j/labs/apoc-*-core.jar /var/lib/neo4j/plugins/
sudo systemctl restart neo4j
Verify that it loaded:
cypher-shell -u neo4j "RETURN apoc.version();"
The output shows the APOC version, which matches the Neo4j version.
Step 9 - Backing up and restoring
In Community Edition, backups are made with neo4j-admin database dump, which requires the database to be stopped. Create a directory owned by the neo4j user:
sudo install -d -o neo4j -g neo4j /var/backups/neo4j
Stop Neo4j, dump the neo4j database, and start it again:
sudo systemctl stop neo4j
sudo -u neo4j neo4j-admin database dump neo4j --to-path=/var/backups/neo4j
sudo systemctl start neo4j
Check that the dump file exists:
ls -lh /var/backups/neo4j
-rw-r--r-- 1 neo4j neo4j 12K Sep 25 10:30 neo4j.dump
To restore, stop the service and load the dump over the existing database:
sudo systemctl stop neo4j
sudo -u neo4j neo4j-admin database load neo4j --from-path=/var/backups/neo4j --overwrite-destination=true
sudo systemctl start neo4j
Copy the dump files to another server or object storage; a backup kept only on the same disk does not protect you from losing the server.
Troubleshooting
neo4j.service fails right after start: read sudo journalctl -u neo4j -n 50 and /var/log/neo4j/neo4j.log. The most common cause is a heap plus page cache larger than the available RAM; lower the values from Step 4.
The client is unauthorized due to authentication failure: set-initial-password only takes effect if it runs before the first start. If Neo4j had already started, the password is still the default neo4j; log in with it and cypher-shell will ask you to choose a new one.
Neo4j Browser connects but queries hang: the browser reaches port 7474 but not Bolt on 7687. Make sure the SSH tunnel forwards both ports.
Conclusion
You installed Neo4j Community Edition from the official repository, kept it private behind an SSH tunnel, sized its memory, built and queried a small graph with constraints and indexes, and set up dumps for backup. Next, you can connect your application with an official driver (Python, JavaScript, Java, Go or .NET), import larger datasets with LOAD CSV or neo4j-admin database import, and schedule the dump from Step 9 with a systemd timer.
