Opengist on AWS User Guide
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
giton your workstation if you want to clone gists m5.largeis the recommended instance type and is what this listing is priced against
Step 1: Launch the instance
From the AWS Marketplace console
- Open the product page in AWS Marketplace and choose Continue to Subscribe, then Continue to Configuration.
- Pick the software version, the Region and the fulfilment option, then Continue to Launch.
- 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. - 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.

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.

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.

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.

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.

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:
- 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.
- 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.

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.