Developer Tools AWS

Opengist on AWS User Guide

| Product: Opengist on AWS

Overview

This image runs Opengist, a self hosted pastebin powered by Git. You paste code or text into a browser and what you get back is not a flat blob: it is a real Git repository you can clone, commit to, push back and browse revision by revision. It is the private answer to a public gist service, running inside your own VPC where the content never leaves your account.

Opengist is distributed under the GNU Affero General Public License version 3. It is installed from the official prebuilt single Go binary at /usr/local/bin/opengist, checksum pinned at build time, with a copy of the licence at /usr/share/doc/opengist/LICENSE. The binary embeds the whole web interface, so there is no Node build, no runtime interpreter and no external database to operate.

The service runs under systemd and starts on boot. All state, including the SQLite database, every gist repository, the full text search index and uploaded avatars, lives under /opt/opengist, which is a dedicated and independently resizable EBS data volume.

The security model, which differs from a stock Opengist install

Port Purpose
22 SSH to the instance.
80 Serves the unauthenticated /healthz probe, and redirects everything else to HTTPS.
443 The web interface and git over HTTPS. This is the only way in to the application.

The application itself is bound to 127.0.0.1:6157 and cannot be reached from outside the instance. An nginx reverse proxy terminates TLS on port 443 and is the sole path to it. There is deliberately no HTTP Basic layer in front: Opengist's REST API authenticates with a Bearer token in the same Authorization header a basic auth layer would consume, so stacking one would break every authenticated request and every git operation. Opengist's own session and token authentication is the control.

Every instance generates its own administrator password, its own session signing secret and its own TLS certificate at first boot. The password is written to /root/opengist-credentials.txt with mode 0600 and is seeded through the application's own command line, so it never appears in the process list. Nothing is baked into the image, so two instances launched from it never share a secret.

This image ships locked down. Upstream Opengist lets anyone who can reach an instance register an account, and the first person to register becomes the administrator. On an instance that gets a public address the moment it launches, that is a race and an abuse magnet, so three policies are applied before the service ever starts:

Policy Shipped as Effect
Disable signup On Nobody can self register. The administrator creates accounts.
Require login On Nothing is readable without an account.
Allow individual gists without login Off Individual gists are not anonymously readable.

Each is a single toggle on the Admin panel, Configuration tab, and Step 7 walks through relaxing them if you want a more open instance.

Two upstream listeners are switched off because neither is appropriate for a machine image. The built in SSH git server, which upstream enables by default on 0.0.0.0:2222, is disabled: shipping it would put a second unrequested port on the image and would give every customer instance the same SSH host identity. Git works over HTTPS instead. The Prometheus metrics endpoint, which defaults to 0.0.0.0:6158, is also disabled.

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and ports 80 and 443 from wherever your users are
  • An SSH client, and git on your workstation if you want to clone gists
  • m5.large is the recommended instance type and is what this listing is priced against

Step 1: Launch the instance

From the AWS Marketplace console

  1. Open the product page in AWS Marketplace and choose Continue to Subscribe, then Continue to Configuration.
  2. Pick the software version, the Region and the fulfilment option, then Continue to Launch.
  3. Choose Launch through EC2, select m5.large, your key pair, your VPC and subnet, and a security group that allows inbound 22, 80 and 443.
  4. Launch the instance and wait for both status checks to pass.

From the AWS CLI

Replace the placeholder values with the AMI id shown on the listing, your key pair, your subnet and your security group.

aws ec2 run-instances \
  --image-id ami-xxxxxxxxxxxxxxxxx \
  --instance-type m5.large \
  --key-name your-key-pair \
  --subnet-id subnet-xxxxxxxxxxxxxxxxx \
  --security-group-ids sg-xxxxxxxxxxxxxxxxx \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=opengist}]'

First boot takes a few seconds after the instance reaches the running state. It resolves this instance's public address, mints the session secret and the administrator password, seeds the account, issues the TLS certificate and writes the credentials file. Everything below assumes it has finished.

Step 2: Connect over SSH and read your credentials

The login user depends on the AMI variant you launched.

Variant SSH login user
Ubuntu 24.04 ubuntu

Connect with your key pair, replacing the address with your own instance:

ssh -i your-key.pem ubuntu@<public-ip>

The login banner tells you where things are. Read this instance's unique administrator credentials:

sudo cat /root/opengist-credentials.txt

The file is readable by root only. Confirm the account name and the URLs it recorded for this instance:

sudo grep -E '^OPENGIST_(ADMIN_USER|URL|HEALTH_URL|APP_LISTEN)=' /root/opengist-credentials.txt
OPENGIST_ADMIN_USER=admin
OPENGIST_URL=https://44.201.83.181/
OPENGIST_HEALTH_URL=https://44.201.83.181/healthz
OPENGIST_APP_LISTEN=127.0.0.1:6157

The password lives in the same file on the line beginning OPENGIST_ADMIN_PASSWORD=. Keep it out of your shell history and change it from the account settings page once you have signed in.

Step 3: Sign in to the web interface

Open https://<public-ip>/ in a browser. The certificate was issued for this instance at first boot and is self signed, so your browser warns on the first visit; Step 8 replaces it with your own certificate. Accept the warning and you reach the login page.

Opengist login page

Sign in with the username and password from /root/opengist-credentials.txt. Because open registration is disabled in this image, the register link does not offer a usable form.

After signing in you land on the gist index. On a freshly launched instance it is empty; the screenshot below shows an instance with a few snippets already saved, each rendered with syntax highlighting.

The gist index

Step 4: Create your first snippet

Choose New in the top navigation. Give the file a name with an extension, because that is what selects the highlighter, then paste your content. Expand Metadata to add a title, a description, a URL and topics, and use Expires if the snippet should delete itself later.

Creating a new gist

The split button at the bottom right chooses the visibility as you create it:

Visibility Who can see it
Public Listed on the index and searchable by anyone signed in
Unlisted Reachable only by its URL, not listed
Private Visible only to you

Save it and you get the gist page: the file rendered with highlighting, a Revisions tab, an Embed box for putting the snippet into a wiki or documentation page, Raw and Download ZIP links, and edit, archive and delete actions.

A saved gist

Step 5: Clone a gist with git

This is the part that makes Opengist different from an ordinary paste service. Every gist is a real Git repository served over HTTPS at the same address as the web interface. Copy the gist URL from your browser and clone it, replacing the placeholders with your instance address, your username and the gist id:

git clone https://<public-ip>/<user>/<gist-id>

Git prompts for your Opengist username and password. Because the certificate is self signed until you replace it, add -c http.sslVerify=false on the first clone, or trust the certificate on your workstation.

Edit the file, commit and push, and the new revision appears on the gist's Revisions tab immediately:

cd <gist-id>
git commit -am "tighten the retry loop"
git push

The verification section below proves this whole round trip automatically against your own running instance.

Step 6: Search across your snippets

The search box at the top of every page queries the built in full text index. As well as free text you can scope a search by field:

Query Finds
systemctl Snippets whose content mentions systemctl
language:go Snippets containing Go files
extension:yml Snippets containing YAML files
filename:backend.tf Snippets containing that filename
user:alice Snippets owned by that user
topic:platform Snippets tagged with that topic

Step 7: Add users, and choose how open this instance should be

Open Admin panel from the account menu. The Configuration tab shows the effective configuration on the left and the policy toggles on the right. The three that this image changes are the top three.

Admin panel, Configuration

Confirm the shipped policy on your own instance:

sudo sqlite3 -readonly /opt/opengist/opengist.db 'select key,value from admin_settings order by key;'
allow-gists-without-login|0
disable-gravatar|0
disable-login-form|0
disable-signup|1
require-login|1

Adding people, without opening registration to the internet, in order of preference:

  1. Invitations. The Invitations tab creates a time limited invitation link with a use count. Send the link to a colleague and they create their own account, and nobody else can.
  2. Create the account yourself. On the instance:

text sudo opengist --config /etc/opengist/config.yml admin create-user --username alice --password-stdin

The command reads the password from standard input so it never lands in your shell history or the process list. Add --admin to grant administrator rights. 3. Connect your identity provider. Opengist authenticates against OpenID Connect, LDAP, GitHub, GitLab or Gitea. Add the provider keys to /etc/opengist/config.yml and restart the service. With oidc.admin-group set, membership of that group grants administrator rights automatically.

Only turn Disable signup off if this instance is genuinely meant to accept public registrations. Turning Require login off makes public gists readable without an account, which is what you want for a shared internal paste board; turning on Allow individual gists without login instead keeps the index private while letting anyone with a link read a single gist.

The Users and Gists tabs list and manage everything on the instance, and the General tab carries maintenance actions such as reindexing and garbage collecting repositories.

Admin panel

Step 8: Replace the self signed certificate

The certificate issued at first boot carries this instance's address and is self signed. Once you have a DNS name pointing at the instance, drop your own certificate and key in place and reload nginx. The paths are fixed, so nothing else needs editing:

File Contents
/etc/nginx/tls/opengist.crt Your certificate, followed by any intermediates
/etc/nginx/tls/opengist.key The matching private key, mode 0600 and owned by root

After copying them in:

sudo nginx -t
sudo systemctl reload nginx

Then set the public URL so links, redirects and clone URLs use your domain rather than the IP address. Edit external-url in /etc/opengist/config.yml, with no trailing slash, and restart:

sudo sed -i 's#^external-url:.*#external-url: https://gists.example.com#' /etc/opengist/config.yml
sudo systemctl restart opengist

A trailing slash there produces double slash redirects that return 404, so leave it off.

Verifying the deployment

Every block below runs on the instance and asserts rather than just printing, so a non zero exit means something is genuinely wrong.

Both services should be active:

sudo systemctl is-active opengist.service nginx.service
active
active

The application must be listening on loopback only, with just SSH and the two web ports reachable from outside. Note 127.0.0.1:6157, not 0.0.0.0:6157, and that nothing is listening on 2222 or 6158:

sudo ss -Hltnp | awk '{print $4}' | sort
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:6157
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80

The health endpoint answers unauthenticated on 443, and port 80 redirects:

curl -sk -o /dev/null -w 'healthz HTTP %{http_code}\n' https://127.0.0.1/healthz
curl -s -o /dev/null -w 'port 80 HTTP %{http_code}\n' http://127.0.0.1/
healthz HTTP 200
port 80 HTTP 301

The version and the licence artifact that ships with the image:

/usr/local/bin/opengist version
head -1 /usr/share/doc/opengist/LICENSE
Opengist v1.14.0
                    GNU AFFERO GENERAL PUBLIC LICENSE

The configuration keeps the application on loopback and the two optional listeners off:

sudo grep -E '^(http\.host|http\.port|ssh\.git-enabled|metrics\.enabled|index)' /etc/opengist/config.yml
index: bleve
http.host: 127.0.0.1
http.port: 6157
ssh.git-enabled: disabled
metrics.enabled: false

The certificate is this instance's own, not a shared one:

sudo openssl x509 -in /etc/nginx/tls/opengist.crt -noout -subject -dates
subject=C = GB, O = cloudimg, CN = 44.201.83.181
notBefore=Jul 26 21:24:40 2026 GMT
notAfter=Jul 23 21:24:40 2036 GMT

The full credential and round trip check

The image ships a self test that proves, by exit code, that an unauthenticated caller is refused, that a wrong password grants no session, that this instance's own generated password is accepted through the same code path, and that a real snippet can be created, read back over HTTP and cloned over git. It creates a temporary gist and deletes it again, so it leaves no state behind:

sudo /usr/local/sbin/opengist-selftest.sh
OK anonymous refused (api 401, settings and index land on /login), sign-up disabled and proven to create no account, wrong password grants no session, this instance's own password accepted, gist round trip verified over HTTP and over a real git clone, exposure clean

The data volume, backup and growth

Everything Opengist owns lives on a dedicated EBS volume mounted at /opt/opengist by filesystem UUID, so it survives an instance replacement and can be resized without touching the operating system disk:

df -h /opt/opengist | tail -1
grep ' /opt/opengist ' /etc/fstab
/dev/nvme1n1     40G  948K   38G   1% /opt/opengist
UUID=96f498c5-4cc9-4348-b2f7-9ff1dd98bce8 /opt/opengist ext4 defaults,nofail 0 2

What is on it:

Path Contents
/opt/opengist/opengist.db Accounts, gist metadata, sessions, access tokens and the admin settings
/opt/opengist/repos One real Git repository per gist
/opt/opengist/index The bleve full text search index

For backup, take an EBS snapshot of the data volume. For a consistent copy of the database while the service is running, use SQLite's own online backup rather than copying the file:

sudo sqlite3 /opt/opengist/opengist.db ".backup '/var/backups/opengist-$(date -u +%Y%m%d).db'"

To grow the volume, modify it in the EC2 console or CLI and then extend the filesystem on the instance:

sudo resize2fs /dev/nvme1n1

The search index is derived data. If it is ever lost or looks stale, rebuild it from the General tab of the admin panel rather than restoring it.

Service management and troubleshooting

Task Command
Service status sudo systemctl status opengist nginx
Restart the application sudo systemctl restart opengist
Reload the proxy after a certificate change sudo systemctl reload nginx
Application logs sudo journalctl -u opengist -n 100
Proxy access and error logs sudo tail -n 50 /var/log/nginx/error.log
First boot log sudo journalctl -u opengist-firstboot
Re-check exposure sudo /usr/local/sbin/opengist-exposure-check.sh '22 80 443'

The browser warns about the certificate. Expected until Step 8. The certificate is generated on your instance and is not shared with anyone.

The credentials file only contains comments. First boot has not finished. Check it with:

sudo systemctl status opengist-firstboot.service

Links or clone URLs point at the wrong address. external-url in /etc/opengist/config.yml is set from the public address the instance had at first boot. If the address changed, or you moved to a DNS name, update it as shown in Step 8 and restart the service.

A push is rejected as too large. The proxy accepts bodies up to 512 MB. Raise client_max_body_size in /etc/nginx/sites-available/cloudimg-opengist if you need more, then reload nginx.

Nobody can sign up. That is the shipped policy. Use an invitation, create the account from the command line, or turn the toggle off, all covered in Step 7.

Support

cloudimg provides 24/7 technical support for this image by email at support@cloudimg.co.uk and by live chat on www.cloudimg.co.uk. We can help with deployment, TLS certificates, identity provider integration, git over HTTPS, access tokens and the REST API, search index tuning, data volume resizing and backup, and hardening.

Opengist itself is community supported upstream at github.com/thomiceli/opengist, with documentation at opengist.io.