This guide walks you through setting up a complete Ogmara node — from a bare server to a fully-featured deployment with media storage, push notifications, and a web frontend. Each step builds on the previous one, but you only need to complete Step 1 and Step 2 to have a working node on the network.
A complete Ogmara node deployment consists of five components. The L2 node is the only required piece — everything else adds capabilities.
Full Ogmara Node Stack:
1. L2 Node (core) — stores messages, connects to network
2. IPFS (media) — image/video uploads and serving
3. Push Gateway (notif.) — mobile push notifications
4. Web Frontend (access) — serve the web app to users
5. Reverse Proxy (prod.) — SSL, domain name, routing
Step 1 alone gives you a working node.
Each additional step adds features.
You can add components incrementally. Start with just the L2 node, verify it works, then add IPFS, push notifications, and the web frontend when you are ready.
Update your system and install the build tools needed to compile the L2 node from source. If you plan to use Docker only, you can skip the Rust toolchain.
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install build dependencies
sudo apt install -y build-essential pkg-config libssl-dev \
curl git unzip
# Install Rust (if building from source)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# Install Node.js (if deploying web frontend)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
Run Ogmara under a dedicated system user for security. Never run the node as root.
# Create a system user for Ogmara
sudo useradd -r -m -d /var/lib/ogmara -s /usr/sbin/nologin ogmara
# Create required directories
sudo mkdir -p /var/lib/ogmara/data
sudo mkdir -p /etc/ogmara
sudo mkdir -p /var/log/ogmara
# Set ownership
sudo chown -R ogmara:ogmara /var/lib/ogmara
sudo chown -R ogmara:ogmara /var/log/ogmara
If your server has only 2 GB of RAM, add swap space before compiling Rust projects. The Rust compiler can use significant memory during builds.
# Create a 2 GB swap file
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Configure UFW to allow only the ports your node needs.
# Install and enable UFW
sudo apt install -y ufw
# Allow SSH (so you don't lock yourself out)
sudo ufw allow OpenSSH
# Allow Ogmara P2P
sudo ufw allow 41720/tcp
sudo ufw allow 41720/udp
# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enable
sudo ufw status
Important: Make sure you allow SSH before enabling UFW. If you skip this, you will lock yourself out of the server.
The L2 node is the core of your Ogmara deployment. It stores messages, relays them to peers, verifies wallet signatures, and serves the API that clients connect to.
Docker is the fastest way to get running. The image is published to Docker Hub under the ogmara/ogmara repository with per-component, per-version tags.
# Pull the v0.46.1 L2 node image (always pin to a specific version
# for reproducible deployments — check
# https://hub.docker.com/r/ogmara/ogmara/tags for the current latest)
docker pull ogmara/ogmara:l2-node-0.46.1
# Create named volumes for persistent data and config
docker volume create ogmara-data
docker volume create ogmara-config
# Run the node. On first start the entrypoint auto-generates a default
# config at /etc/ogmara/ogmara.toml inside the container (since v0.46.1).
# You'll edit it in the next subsection.
docker run -d \
--name ogmara-l2 \
--restart unless-stopped \
-p 41720:41720/tcp \
-p 41720:41720/udp \
-p 41721:41721/tcp \
-v ogmara-data:/data \
-v ogmara-config:/etc/ogmara \
ogmara/ogmara:l2-node-0.46.1
The auto-generated default starts with safe values (loopback API, anchoring + metadata publication disabled, PoW spam-protection enabled) but you must fill in the [klever] URLs and contract address before the node can interact with the chain — see the Configuration subsection below.
If you prefer to compile the binary yourself, clone the repository and build with Cargo.
# Clone the L2 node repository
git clone https://github.com/Ogmara/l2-node.git
cd l2-node
# Build in release mode
cargo build --release
# Copy binary to system path
sudo cp target/release/ogmara-node /usr/local/bin/
sudo chmod +x /usr/local/bin/ogmara-node
Create a systemd service file so the node starts automatically on boot.
# /etc/systemd/system/ogmara-l2.service
[Unit]
Description=Ogmara L2 Node
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ogmara
Group=ogmara
ExecStart=/usr/local/bin/ogmara-node --config /etc/ogmara/ogmara.toml
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
WorkingDirectory=/var/lib/ogmara
# Security hardening
ProtectSystem=strict
ReadWritePaths=/var/lib/ogmara/data /var/log/ogmara
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable ogmara-l2
sudo systemctl start ogmara-l2
The configuration file lives at /etc/ogmara/ogmara.toml. As of v0.46.1, the Docker container auto-generates a default annotated config on first start (and source builds can use ogmara-node init to do the same). Either way you only need to fill in the four blocks below to get connected.
For Docker — copy the auto-generated config out, edit it, and copy it back:
# Pull the auto-generated default out of the running container
docker cp ogmara-l2:/etc/ogmara/ogmara.toml ./ogmara.toml
# Edit the four blocks shown below (use your favourite editor)
nano ./ogmara.toml
# Push the edited config back and restart
docker cp ./ogmara.toml ogmara-l2:/etc/ogmara/ogmara.toml
docker restart ogmara-l2
For source builds — generate, edit, install:
# Generate a default config (the same default the docker container uses)
ogmara-node init --output /etc/ogmara/ogmara.toml
# Edit it
sudo nano /etc/ogmara/ogmara.toml
Minimum changes needed in the auto-generated config to connect to the network:
# /etc/ogmara/ogmara.toml — minimum changes from the auto-generated default
[api]
# REQUIRED for [anchoring.metadata] auto-derive and for being reachable
# from clients. Set to the public URL where this node's API is served
# (typically via the reverse proxy you configure in Step 6). v0.46.0
# Phase D supports bracketed IPv6: "http://[2001:db8::1]:41721".
public_url = "https://node.yourdomain.com"
[klever]
# Klever mainnet endpoints
node_url = "https://node.mainnet.klever.org"
api_url = "https://api.mainnet.klever.org"
# Ogmara smart contract address on Klever mainnet
contract_address = "klv1qqqqqqqqqqqqqpgq8c9yag9vuc2pe64fwvqsq9e8ul8w5zuglf5qfgh7z3"
The auto-generated default exposes every operator-tunable knob with inline comments — on-chain state anchoring ([anchoring]), on-chain peer-discovery publication ([anchoring.metadata]), staleness filters, media cache tuning, alert dispatchers (Telegram / Discord / webhook), and snapshot sync. Browse the full annotated reference on GitHub: ogmara.example.toml. See also § Optional Production Features below.
For testing and development, use Klever testnet: change the URLs to https://node.testnet.klever.org and https://api.testnet.klever.org, and use the testnet contract address klv1qqqqqqqqqqqqqpgq0ja2j7xwz843ryfsk9vlz6xzsaak590h6pgq7nwr02.
Testnet and mainnet nodes run on isolated networks — they cannot peer with each other. The node auto-detects which network it belongs to from the Klever URLs in your config. If you started with testnet and want to switch to mainnet (or vice versa), you must wipe the data directory because testnet and mainnet have separate channel IDs, chain state, and peer lists that would collide if mixed.
The data directory contains your node's private key — this IS your node's wallet address. Deleting it without a backup means any KLV funds on that address are permanently lost and your anchoring authorization must be re-established. Always export the key before deleting data.
# --- Source / systemd install ---
# 1. Stop the node
sudo systemctl stop ogmara-l2
# 2. BACK UP YOUR NODE KEY (critical!)
ogmara-node export-key -o my-node-key.bak
# 3. Edit ogmara.toml — switch Klever URLs and contract address
# 4. Wipe the data directory (path depends on your `[node] data_dir`)
sudo rm -rf /var/lib/ogmara/data
# 5. Start the node
sudo systemctl start ogmara-l2
# 6. Restore your key (keeps the same wallet address)
ogmara-node import-key -i my-node-key.bak
# Then restart to apply the restored key
sudo systemctl restart ogmara-l2
For Docker installations (using the named volumes from Step 2):
# 1. Back up the key first (out of the data volume)
docker exec ogmara-l2 ogmara-node export-key -o /data/node-key.bak
docker cp ogmara-l2:/data/node-key.bak ./my-node-key.bak
# 2. Stop + remove the container (keeps the volumes intact)
docker stop ogmara-l2
docker rm ogmara-l2
# 3. Wipe ONLY the chain/messages data volume (keeps the config volume
# so you don't have to re-edit it from scratch)
docker volume rm ogmara-data
docker volume create ogmara-data
# 4. Edit your config volume to point at the new network
docker run --rm -v ogmara-config:/etc/ogmara busybox \
sed -i 's|node.testnet.klever.org|node.mainnet.klever.org|g; \
s|api.testnet.klever.org|api.mainnet.klever.org|g; \
s|klv1qqqqqqqqqqqqqpgq0ja2j7xwz843ryfsk9vlz6xzsaak590h6pgq7nwr02|klv1qqqqqqqqqqqqqpgq8c9yag9vuc2pe64fwvqsq9e8ul8w5zuglf5qfgh7z3|g' \
/etc/ogmara/ogmara.toml
# (Or `docker cp` the file out, edit, copy back — same as the
# Configuration subsection above. The contract address swap shown
# is testnet -> mainnet; reverse for mainnet -> testnet.)
# 5. Recreate the container (same volumes)
docker run -d --name ogmara-l2 --restart unless-stopped \
-p 41720:41720/tcp -p 41720:41720/udp -p 41721:41721/tcp \
-v ogmara-data:/data -v ogmara-config:/etc/ogmara \
ogmara/ogmara:l2-node-0.46.1
# 6. Restore the key
docker cp ./my-node-key.bak ogmara-l2:/data/node-key.bak
docker exec ogmara-l2 ogmara-node import-key -i /data/node-key.bak
docker restart ogmara-l2
Messages and chain state rebuild automatically from the network. Only the private key needs to be backed up — everything else re-syncs via the chain scanner and peer sync protocol.
After starting the node, check the logs to confirm it is running correctly.
# Check the service status
sudo systemctl status ogmara-l2
# View logs (for systemd)
sudo journalctl -u ogmara-l2 -f
# View logs (for Docker)
docker logs -f ogmara-l2
You should see log entries indicating:
# Quick health check
curl http://127.0.0.1:41721/api/v1/health
If you get a valid JSON response, your L2 node is up and running. You now have a working Ogmara node on the network.
Your node generated a private key on first startup. This key IS your node's wallet address — it holds KLV funds for anchoring and is your identity on the network. If you ever lose the data directory, this key is gone forever along with any funds on it.
# Export your node's private key to a backup file
ogmara-node export-key -o my-node-key.bak
# Store this file securely (USB drive, password manager, etc.)
# NEVER share it or commit it to git
To restore on a new server or after a data wipe:
ogmara-node import-key -i my-node-key.bak
A bare node connects to the network and serves your users. To participate more fully — help anchor chain state, appear in the network's peer-discovery registry, get paged when something goes wrong — opt into these features in your ogmara.toml. All are off by default; flip them on once you understand the trade-offs.
| Feature | Config section | What it does |
|---|---|---|
| State anchoring | [anchoring] |
Periodically submits a Merkle root of your node's L2 state to the Ogmara smart contract on Klever. Other nodes verify against your submissions; reaching quorum makes a root canonical. Requires KLV in the anchorer wallet for transaction fees. |
| On-chain peer discovery | [anchoring.metadata] |
Publishes your node's libp2p multiaddrs on chain so other nodes can discover you without relying on a hardcoded bootstrap list. Auto-derives from [api] public_url + [network] listen_port (IPv4, IPv6, or DNS). Optional — non-publishers still anchor normally. |
| Graceful pause on shutdown | [anchoring] pause_on_shutdown |
SIGTERM handler signs and broadcasts a pauseNode transaction before exit so other nodes stop counting yours toward quorum during planned downtime. Opt-in because it requires the wallet key in process memory for shutdown signing. |
| Alert dispatch | [alerts], [alerts.telegram], [alerts.discord], [alerts.webhook] |
Push critical / warning / info alerts (disk usage, divergence, sync lag, etc.) to Telegram, Discord, or a generic webhook. Bot tokens and webhook URLs should be loaded from environment variables, not the config file. |
| Snapshot sync | [snapshot] |
Default-on. Fresh nodes bootstrap by fetching a peer-served Merkle-rooted state snapshot rather than replaying millions of Klever blocks. Disable serving on resource-constrained nodes if needed. |
| Peer staleness filter | [network.discovery] |
Tune how aggressively the node drops dial candidates whose last on-chain anchor is older than N days. Default 7. Local dev deployments may want a longer threshold. |
Each section is fully documented with inline comments in the auto-generated config and in the GitHub reference: ogmara.example.toml. After editing the config, restart the node (docker restart ogmara-l2 or sudo systemctl restart ogmara-l2) for changes to take effect — the L2 node reads its config once at startup.
IPFS handles media storage for Ogmara — images, videos, avatars, and file attachments. Without IPFS, your node can still relay text messages, but users connected to it will not be able to upload or view media content.
Kubo is the reference implementation of IPFS. Download and install the latest version.
# Download Kubo
wget https://dist.ipfs.tech/kubo/v0.33.2/kubo_v0.33.2_linux-amd64.tar.gz
tar -xzf kubo_v0.33.2_linux-amd64.tar.gz
cd kubo
sudo bash install.sh
# Initialize IPFS as the ogmara user
sudo -u ogmara IPFS_PATH=/var/lib/ogmara/.ipfs ipfs init --profile server
# Verify installation
sudo -u ogmara IPFS_PATH=/var/lib/ogmara/.ipfs ipfs version
Adjust the IPFS configuration for a server environment. Set a storage limit and configure the gateway.
# Set storage limit (adjust based on your available disk space)
sudo -u ogmara IPFS_PATH=/var/lib/ogmara/.ipfs \
ipfs config Datastore.StorageMax "10GB"
# Listen on localhost only for the API (security)
sudo -u ogmara IPFS_PATH=/var/lib/ogmara/.ipfs \
ipfs config Addresses.API "/ip4/127.0.0.1/tcp/5001"
# Configure the gateway to listen on localhost
sudo -u ogmara IPFS_PATH=/var/lib/ogmara/.ipfs \
ipfs config Addresses.Gateway "/ip4/127.0.0.1/tcp/8080"
Security: NEVER expose port 5001 (the IPFS API) to the public internet. This port provides full read/write access to your IPFS node, including the ability to add, pin, and delete content. It must only be accessible from localhost or through a secured tunnel.
# /etc/systemd/system/ipfs.service
[Unit]
Description=IPFS Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ogmara
Group=ogmara
Environment=IPFS_PATH=/var/lib/ogmara/.ipfs
ExecStart=/usr/local/bin/ipfs daemon
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
# Enable and start IPFS
sudo systemctl daemon-reload
sudo systemctl enable ipfs
sudo systemctl start ipfs
# Verify it is running
sudo systemctl status ipfs
curl -s http://127.0.0.1:5001/api/v0/id | head -c 200
Add the IPFS section to your ogmara.toml configuration file.
# In /etc/ogmara/ogmara.toml (auto-generated default already has the
# [ipfs] block; just confirm api_url and gateway_url point at your
# running IPFS daemon, and adjust the limits if needed)
[ipfs]
api_url = "http://127.0.0.1:5001"
gateway_url = "http://127.0.0.1:8080"
# Maximum upload size in MB (default: 50)
max_upload_size_mb = 50
# Optional media handler tuning — defaults are safe for a ~4 GiB-RAM
# VPS. Lower for resource-constrained nodes; raise for high-readership
# deployments. See the annotated example on github for the full list:
# https://github.com/Ogmara/l2-node/blob/main/ogmara.example.toml
# media_cache_total_mb = 256 # Total LRU weight cap
# media_handler_permits = 32 # Concurrent /api/v1/media/:cid handlers
# media_per_ip_permits = 4 # Per-client-IP sub-cap
# Restart the L2 node to pick up the new config
sudo systemctl restart ogmara-l2
# (or for docker) docker restart ogmara-l2
If you want to run IPFS on a separate server (for example, a machine with more storage), you can connect the L2 node to it securely using a WireGuard VPN tunnel or an SSH tunnel.
# SSH tunnel example: forward local port 5001 to remote IPFS
ssh -N -L 5001:127.0.0.1:5001 user@ipfs-server.example.com
# Then configure ogmara.toml to use 127.0.0.1:5001 as usual
For a permanent remote IPFS setup, use WireGuard instead of SSH tunnels. WireGuard is faster, more reliable, and reconnects automatically. See the deployment documentation for detailed WireGuard configuration.
The push gateway enables mobile push notifications for Android and iOS users connected to your node. Without it, mobile users will only receive messages when the app is open.
# Pull the push gateway image
docker pull ogmara/ogmara:push-gateway-latest
# Run the push gateway
docker run -d \
--name ogmara-push \
--restart unless-stopped \
-p 127.0.0.1:41722:41722/tcp \
-v /etc/ogmara/push-gateway.toml:/etc/ogmara/push-gateway.toml:ro \
ogmara/ogmara:push-gateway-latest
# Clone and build
git clone https://github.com/Ogmara/push-gateway.git
cd push-gateway
cargo build --release
# Install the binary
sudo cp target/release/ogmara-push /usr/local/bin/
sudo chmod +x /usr/local/bin/ogmara-push
VAPID (Voluntary Application Server Identification) keys are required for web push notifications. Generate a key pair that will identify your server to push services.
# Generate VAPID keys (using the push gateway binary)
ogmara-push generate-vapid-keys
# This outputs a public key and a private key.
# Save both — you will need the public key in client apps
# and the private key in the push gateway config.
# /etc/ogmara/push-gateway.toml
[server]
listen_addr = "127.0.0.1:41722"
[vapid]
private_key = "YOUR_VAPID_PRIVATE_KEY"
public_key = "YOUR_VAPID_PUBLIC_KEY"
subject = "mailto:admin@yourdomain.com"
[auth]
# Shared secret between L2 node and push gateway
# Generate with: openssl rand -hex 32
shared_secret = "YOUR_SHARED_SECRET"
Edit the [push_gateway] block in your L2 node configuration (the auto-generated default ships with enabled = false and empty values).
# In /etc/ogmara/ogmara.toml
[push_gateway]
enabled = true
url = "http://127.0.0.1:41722"
# Loaded from $OGMARA_PUSH_AUTH_TOKEN env var in production
# (preferred over putting the secret in the config file)
auth_token = "YOUR_SHARED_SECRET"
# /etc/systemd/system/ogmara-push.service
[Unit]
Description=Ogmara Push Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ogmara
Group=ogmara
ExecStart=/usr/local/bin/ogmara-push --config /etc/ogmara/push-gateway.toml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable ogmara-push
sudo systemctl start ogmara-push
# Restart the L2 node to connect to the push gateway
sudo systemctl restart ogmara-l2
Important: The shared secret must be identical in both the push gateway config and the L2 node config. Use a strong random value generated with openssl rand -hex 32. Never commit this secret to version control.
Deploying the web frontend lets users access Ogmara through a browser on your domain. The web app is a static single-page application (SPA) that connects to your L2 node's API.
# Clone and build the SDK (shared library used by the web app)
git clone https://github.com/Ogmara/sdk-js.git
cd sdk-js
npm install
npm run build
cd ..
# Clone and build the web frontend
git clone https://github.com/Ogmara/web.git
cd web
npm install
# Link the local SDK
npm link ../sdk-js
# Build for production
npm run build
Copy the built files to a directory that your web server can serve. The standard location is under /var/www/ogmara/app/.
# Create the web root directory
sudo mkdir -p /var/www/ogmara/app
# Copy the built files
sudo cp -r web/dist/* /var/www/ogmara/app/
# Set correct ownership and permissions
sudo chown -R www-data:www-data /var/www/ogmara
sudo chmod -R 755 /var/www/ogmara
The web app is a static SPA. It does not need a Node.js server in production — it is served as plain HTML, CSS, and JavaScript files by your reverse proxy (configured in the next step).
A reverse proxy handles SSL termination, routes requests to the correct backend service, and serves the web frontend. This step shows configuration for both Apache and Nginx — choose whichever you prefer.
Install Certbot with the plugin for your web server. Using the web server plugin (instead of --standalone) is important — it lets Certbot obtain and renew certificates while your web server keeps running.
# For Apache:
sudo apt install -y certbot python3-certbot-apache
# For Nginx:
sudo apt install -y certbot python3-certbot-nginx
Obtain a certificate using your web server plugin. This handles the domain verification challenge through your running web server, so there is no need to stop it.
# For Apache (replace with your domain):
sudo certbot --apache -d node.yourdomain.com
# For Nginx (replace with your domain):
sudo certbot --nginx -d node.yourdomain.com
Do not use certbot certonly --standalone if you already have a web server running. Standalone mode needs to bind port 80 itself, which will fail if Apache or Nginx is already listening on it. This also means renewals will fail later. Always use the --apache or --nginx plugin instead.
Let's Encrypt certificates expire every 90 days. Certbot sets up automatic renewal when installed, but the mechanism depends on how it was installed. Verify that renewal is configured and working.
# On Debian/Ubuntu with apt-installed certbot, renewal runs via cron:
cat /etc/cron.d/certbot
# On systems with snap-installed certbot, renewal runs via systemd timer:
sudo systemctl status certbot.timer
# Test that renewal works (dry run — does not actually renew)
sudo certbot renew --dry-run
If neither the cron job nor the systemd timer exists, create a cron job manually.
# Create a cron job that checks for renewal twice daily
echo '0 0,12 * * * root certbot renew --quiet' | sudo tee /etc/cron.d/certbot
After renewal, your web server needs to reload to pick up the new certificate. Add a deploy hook so this happens automatically.
# For Nginx:
sudo sh -c 'printf "#!/bin/sh\nsystemctl reload nginx\n" > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh'
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# For Apache:
sudo sh -c 'printf "#!/bin/sh\nsystemctl reload apache2\n" > /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh'
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh
The dry run (sudo certbot renew --dry-run) must succeed for all your certificates. If any fail, check that each certificate is using the correct authenticator. You can see and fix this in the renewal config files at /etc/letsencrypt/renewal/*.conf — look for the authenticator line and make sure it says apache or nginx, not standalone.
If you already obtained a certificate using --standalone and renewals are failing because port 80 is in use, you can switch the authenticator without re-issuing the certificate.
# Edit the renewal config for the affected domain
sudo nano /etc/letsencrypt/renewal/node.yourdomain.com.conf
# Change this line:
# authenticator = standalone
# To:
# authenticator = apache (or nginx)
# Then test:
sudo certbot renew --dry-run
# /etc/nginx/sites-available/ogmara
upstream ogmara_api {
server 127.0.0.1:41721;
}
upstream ogmara_push {
server 127.0.0.1:41722;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name node.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name node.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/node.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/node.yourdomain.com/privkey.pem;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header Referrer-Policy strict-origin-when-cross-origin;
# L2 Node API
location /api/ {
proxy_pass http://ogmara_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket endpoint
location /api/v1/ws {
proxy_pass http://ogmara_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
}
# Push Gateway
location /push/ {
proxy_pass http://ogmara_push;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Block admin endpoints from public access
location /admin/ {
deny all;
return 403;
}
# Web frontend (SPA)
location /app/ {
alias /var/www/ogmara/app/;
try_files $uri $uri/ /app/index.html;
}
# Root redirect
location = / {
return 302 /app/;
}
}
# Enable the site and reload
sudo ln -s /etc/nginx/sites-available/ogmara /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# /etc/apache2/sites-available/ogmara.conf
<VirtualHost *:80>
ServerName node.yourdomain.com
Redirect permanent / https://node.yourdomain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName node.yourdomain.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/node.yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/node.yourdomain.com/privkey.pem
# Security headers
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
# L2 Node API
ProxyPass /api/ http://127.0.0.1:41721/api/
ProxyPassReverse /api/ http://127.0.0.1:41721/api/
# WebSocket
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/api/v1/ws$ ws://127.0.0.1:41721/api/v1/ws [P,L]
# Push Gateway
ProxyPass /push/ http://127.0.0.1:41722/push/
ProxyPassReverse /push/ http://127.0.0.1:41722/push/
# Block admin endpoints
<Location /admin/>
Require all denied
</Location>
# Web frontend (SPA)
Alias /app/ /var/www/ogmara/app/
<Directory /var/www/ogmara/app/>
Require all granted
FallbackResource /app/index.html
</Directory>
# Root redirect
RedirectMatch ^/$ /app/
</VirtualHost>
# Enable required modules and site
sudo a2enmod ssl proxy proxy_http proxy_wstunnel rewrite headers
sudo a2ensite ogmara
sudo apache2ctl configtest
sudo systemctl reload apache2
Security: The /admin/ path must be blocked from public access. The admin endpoints provide node management capabilities that should never be exposed to the internet. The configurations above deny all access to this path.
Run through these checks to confirm each component is working correctly.
# 1. L2 Node — should return JSON with node status
curl -s http://127.0.0.1:41721/api/v1/health
# 2. IPFS — should return peer ID and addresses
curl -s http://127.0.0.1:5001/api/v0/id | python3 -m json.tool
# 3. Push Gateway — should return status
curl -s http://127.0.0.1:41722/health
# 4. Web Frontend — should return the HTML page
curl -s -o /dev/null -w "%{http_code}" https://node.yourdomain.com/app/
# 5. API via reverse proxy — should match direct L2 node response
curl -s https://node.yourdomain.com/api/v1/health
# 6. WebSocket — should upgrade successfully
curl -s -o /dev/null -w "%{http_code}" \
-H "Upgrade: websocket" \
-H "Connection: Upgrade" \
https://node.yourdomain.com/api/v1/ws
# Check all services at once
sudo systemctl status ogmara-l2 ipfs ogmara-push nginx
# Or for Docker deployments
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
Under normal operation with all components running, expect approximately:
Keep an eye on logs during the first few hours of operation to catch any issues early.
# Follow all Ogmara logs
sudo journalctl -u ogmara-l2 -u ogmara-push -u ipfs -f
# Check for errors only
sudo journalctl -u ogmara-l2 -p err --since "1 hour ago"
Your node is now a full participant in the Ogmara network. It stores messages, serves media, delivers push notifications, and provides a web interface for users. Other nodes will discover and connect to yours automatically through the peer-to-peer network.
| Component | Port | Purpose | Required |
|---|---|---|---|
| L2 Node | 41720, 41721 | P2P network, API | Yes |
| IPFS | 5001, 8080 | Media storage | No |
| Push Gateway | 41722 | Notifications | No |
| Web Frontend | Static files | Browser access | No |
| Reverse Proxy | 80, 443 | SSL, routing | Production |