LibreTranslate on AWS User Guide
Overview
This image runs LibreTranslate 1.9.6, a free and open source machine translation engine you host yourself, installed from the project's official PyPI wheel into a dedicated Python virtualenv. LibreTranslate exposes a small REST API and a simple web interface, and it translates entirely on the instance using the offline Argos Translate neural models. Nothing you submit is sent to a third party service, and once the models are on disk the instance can translate with no internet access at all.
The engine is served by nginx over HTTPS on port 443, with LibreTranslate itself bound to the loopback interface only. Port 80 answers a plain HTTP liveness check at /health and 301-redirects everything else to port 443. HTTPS is not decoration here: the API key travels in the request body, so it should not cross a network in clear text.
The API ships key protected, not open. An open translation endpoint on the public internet is a compute abuse risk — anyone who finds it can spend your CPU. Key enforcement is therefore switched on, and it applies to the web interface as well as to programmatic callers. No usable key is baked into the image: on the first boot of every deployed instance a one-shot service generates a fresh random API key and a fresh self signed TLS certificate, unique to that instance, and writes the key to /root/libretranslate-info.txt with mode 0600.
Nine languages ship, as offline models: Arabic, Chinese, English, French, German, Italian, Portuguese, Russian and Spanish. That is a deliberate subset — the full upstream Argos catalogue is one hundred model packages and several further gigabytes. LibreTranslate pivots non English pairs through English, so all 72 cross language directional pairs across those nine resolve. Adding more languages is a documented, supported operation and is covered below.
The engine runs on CPU only. This image does not provide GPU acceleration, and LibreTranslate does not require it.
Storage is tiered across two dedicated EBS volumes so neither the models nor the runtime sit on the operating system disk: the Argos model store at /var/lib/libretranslate and the virtualenv at /opt/libretranslate. Both are mounted by filesystem UUID and each can be resized independently.
A note on what this is: LibreTranslate is a machine translation engine. Its output is well suited to understanding content, powering search, pre translating a catalogue or serving in product features where an automatic translation is appropriate. It is not human translation and it is not certified or sworn translation. Have output reviewed by a competent speaker before relying on it in a legal, medical or safety context.
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 security group allowing inbound TCP 22 from your own address, and TCP 443 from wherever your callers are
The recommended instance type is m5.large. Translation is CPU bound, so see the sizing section at the end before you commit to a size in production.
Connecting to your instance
Connect over SSH on port 22 using the key pair you selected at launch. The login user depends on the operating system variant you launched.
| OS variant | SSH login user | Example |
|---|---|---|
| Ubuntu 24.04 LTS | ubuntu |
ssh -i your-key.pem ubuntu@<public-ip> |
Replace <public-ip> with your instance's public IPv4 address and your-key.pem with the private key file for your EC2 key pair.
Confirm the service is running
Two systemd units make up the running stack: libretranslate (the engine) and nginx (the TLS reverse proxy).
systemctl is-active libretranslate nginx
active
active
Both are enabled, so they come back automatically after a reboot:
systemctl is-enabled libretranslate nginx
enabled
enabled
Confirm the version and licence of what is actually installed:
/opt/libretranslate/venv/bin/pip show libretranslate | grep -E '^(Name|Version|License):'
Name: libretranslate
Version: 1.9.6
License: GNU AFFERO GENERAL PUBLIC LICENSE
The plain HTTP liveness endpoint answers without touching the application, which makes it a good target for a load balancer health check:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/health
HTTP 200
Check what is listening. LibreTranslate is on loopback only; nginx owns the public ports:
sudo ss -tlnp | grep -E ':(80|443|5000)\b'
LISTEN 0 1024 127.0.0.1:5000 0.0.0.0:* users:(("libretranslate",pid=5521,fd=7))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5527,fd=5),("nginx",pid=5526,fd=5),("nginx",pid=5525,fd=5))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=5527,fd=7),("nginx",pid=5526,fd=7),("nginx",pid=5525,fd=7))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=5527,fd=6),("nginx",pid=5526,fd=6),("nginx",pid=5525,fd=6))
LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=5527,fd=8),("nginx",pid=5526,fd=8),("nginx",pid=5525,fd=8))
Your per instance API key
The key was generated on this instance's first boot and is stored in a root only file. You can list the field names in that file without revealing any value:
sudo grep -oE '^[A-Z0-9_]+=' /root/libretranslate-info.txt
LIBRETRANSLATE_API_KEY=
LIBRETRANSLATE_URL=
LIBRETRANSLATE_API_URL=
NOTE=
NOTE2=
To read the key itself, so you can paste it into the web interface or into your own client, run sudo cat /root/libretranslate-info.txt on the instance. That file also records the instance URL and a worked curl example.
Treat the key as a secret: do not commit it, and do not echo it in shell scripts or CI logs. Every example below reads it into a shell variable rather than printing it, which is the habit worth keeping.
The endpoint is closed without a key
This is worth verifying yourself rather than taking on trust. A translation request with no key is refused:
curl -ks -o /dev/null -w 'unauthenticated POST /translate -> HTTP %{http_code}\n' -X POST https://127.0.0.1/translate --data "q=Hello world&source=en&target=es&format=text"
unauthenticated POST /translate -> HTTP 400
LibreTranslate answers 400 when a key is required but absent, and 403 for a key it does not recognise. The web interface is subject to the same rule: open it without setting a key and it will tell you to contact the server operator for one.
Your first translation
Read the key into a variable and post a real translation. This is the round trip that proves the instance works:
KEY=$(sudo grep "^LIBRETRANSLATE_API_KEY=" /root/libretranslate-info.txt | cut -d= -f2-); curl -ks -X POST https://127.0.0.1/translate --data "q=Hello world&source=en&target=es&format=text&api_key=$KEY" | jq .
{
"translatedText": "Hola mundo"
}
The -k flag is there because the instance ships a self signed certificate. Once you install a certificate for your own domain you can drop it.
Language detection works the same way:
KEY=$(sudo grep "^LIBRETRANSLATE_API_KEY=" /root/libretranslate-info.txt | cut -d= -f2-); curl -ks -X POST https://127.0.0.1/detect --data "q=Bonjour tout le monde&api_key=$KEY" | jq .
[
{
"confidence": 100.0,
"language": "fr"
}
]
From your own machine, substitute your instance's address for 127.0.0.1 and pass the key you read above:
curl -k -X POST https://<public-ip>/translate --data "q=Hello world&source=en&target=es&format=text&api_key=<your-api-key>"
The web interface
Browse to https://<public-ip>/ and accept the one time certificate warning. Choose your source and target languages, type into the left hand box, and the translation appears on the right.

Because key enforcement also covers the interface, click Set API Key in the header and paste the key from /root/libretranslate-info.txt the first time you use it. Until you do, the page loads but translation is refused:

Non Latin scripts work exactly the same way:

The instance also serves an interactive REST API reference at https://<public-ip>/docs, listing every endpoint with its parameters and response shape:

Which languages ship
Ask the instance rather than guessing. /languages is the authoritative list:
curl -ks https://127.0.0.1/languages | jq -r '.[] | "\(.code)\t\(.name)"'
en English
ar Arabic
zh-Hans Chinese
fr French
de German
it Italian
pt Portuguese
ru Russian
es Spanish
Nine languages, and every one is reachable from every other:
curl -ks https://127.0.0.1/languages | jq '{languages: length, cross_language_pairs: (([.[].targets|length]|add) - length)}'
{
"languages": 9,
"cross_language_pairs": 72
}
Note the code for Chinese is zh-Hans, the script qualified form. LibreTranslate also accepts the plain zh on /translate, but /languages reports zh-Hans, so match on the prefix if your client builds its language menu from that response.
Those 72 pairs come from 18 Argos model packages — English to and from each of the other eight languages, plus two direct Spanish and Portuguese pairs. LibreTranslate pivots anything else through English, which is why French to German works without a French to German model:
sudo find /var/lib/libretranslate/argos-packages -maxdepth 2 -name metadata.json -type f | wc -l
18
A manifest of exactly which packages are installed is written onto the instance at /var/lib/libretranslate/argos-manifest.txt.
Adding more languages
The full upstream Argos catalogue is one hundred packages. To add one, install it with argospm as the service user, then widen the set the service loads and restart. This example adds Japanese; the model volume is sized for the entire catalogue, so you will not run out of room.
sudo -u libretranslate env HOME=/var/lib/libretranslate ARGOS_PACKAGES_DIR=/var/lib/libretranslate/argos-packages /opt/libretranslate/venv/bin/argospm install translate-en_ja
The service is started with a --load-only list naming the languages it loads into memory. Edit that list in the unit file to include your new language code, then reload and restart:
sudo sed -i 's/--load-only ar,de,en,es,fr,it,pt,ru,zh/--load-only ar,de,en,es,fr,it,pt,ru,zh,ja/' /etc/systemd/system/libretranslate.service && sudo systemctl daemon-reload && sudo systemctl restart libretranslate
Each loaded language costs memory, so add what you need rather than everything, and size the instance accordingly. Confirm the result with /languages again.
Issuing additional API keys
The keys live in a small SQLite database at /var/lib/libretranslate/api_keys.db, with a per key request limit. A freshly launched instance holds exactly the one key first boot generated:
sudo sqlite3 /var/lib/libretranslate/api_keys.db "SELECT count(*) AS keys, min(req_limit) AS req_limit FROM api_keys;"
1|80
Issuing a second key — for a different application, so you can revoke one without disturbing the other — is a single insert. This writes the new key straight into the root only file rather than printing it:
NEWKEY=$(python3 -c 'import uuid;print(uuid.uuid4())'); sudo sqlite3 /var/lib/libretranslate/api_keys.db "INSERT INTO api_keys (api_key, req_limit) VALUES ('$NEWKEY', 80);" && sudo sh -c "umask 077; printf 'SECOND_API_KEY=%s\n' '$NEWKEY' >> /root/libretranslate-info.txt" && echo "second key issued and appended to /root/libretranslate-info.txt"
second key issued and appended to /root/libretranslate-info.txt
Read it back from /root/libretranslate-info.txt when you need it, and give each application its own key so that revoking one never disturbs another. To revoke, delete the row whose key you are retiring — substitute the key itself for <your-api-key>:
sudo sqlite3 /var/lib/libretranslate/api_keys.db "DELETE FROM api_keys WHERE api_key = '<your-api-key>';"
Changes take effect on the next request; no restart is needed.
Replacing the self signed certificate
The certificate generated on first boot is self signed, which is why browsers warn and curl needs -k. Replace it with a certificate for a domain you control. Point a DNS A record at the instance, open port 80 to the internet so the HTTP-01 challenge can complete, then:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d translate.your-domain.example
Certbot rewrites the nginx server block and installs a renewal timer. If you would rather drop in a certificate you already hold, replace these two files and reload nginx — the paths are what the shipped configuration references:
sudo install -m 0644 -o root -g root your-fullchain.pem /etc/nginx/ssl/libretranslate.crt
sudo install -m 0600 -o root -g root your-private-key.pem /etc/nginx/ssl/libretranslate.key
sudo nginx -t && sudo systemctl reload nginx
Storage layout
Neither the models nor the runtime live on the operating system disk:
df -h --output=target,source,size,used,avail / /opt/libretranslate /var/lib/libretranslate
Mounted on Filesystem Size Used Avail
/ /dev/root 38G 3.7G 35G
/opt/libretranslate /dev/nvme1n1 9.8G 474M 8.8G
/var/lib/libretranslate /dev/nvme2n1 40G 3.9G 34G
Both data volumes are referenced in /etc/fstab by filesystem UUID, so the layout reproduces on every instance launched from this image and survives device renaming. To grow either one, modify the EBS volume in the console and then run sudo resize2fs against the device shown above.
The only state worth backing up is /var/lib/libretranslate — it holds the models and your API keys. An EBS snapshot of that volume is sufficient.
Rate limits and tuning
The service starts with a request limit of 80 per minute per key and a 5000 character cap per request, and it runs two translation threads. All three are arguments in the unit file:
grep -E 'req-limit|char-limit|threads' /etc/systemd/system/libretranslate.service
--req-limit 80 --char-limit 5000 --threads 2
Raise --threads towards the instance's vCPU count if you are throughput bound and have memory to spare, then sudo systemctl daemon-reload && sudo systemctl restart libretranslate. Raise --char-limit if you submit long documents, bearing in mind a single large request occupies a thread for its whole duration.
Sizing for throughput
Translation here is CPU bound, with no GPU involved. Two properties follow, and both matter more than instance family choice:
- Throughput scales with vCPU count and with
--threads. If you need more translations per second, add vCPUs and raise the thread count together; raising one without the other will not help. - Memory scales with the number of languages loaded, not with request volume. Each language in
--load-onlyis held in memory. Loading the full catalogue on a small instance is the most common way to run out of RAM.
For bulk work, batching many short requests concurrently against a larger instance is more efficient than sending one very long request, because a single request cannot use more than one thread. If you need horizontal scale, run several instances behind a load balancer pointed at the /health endpoint on port 80 — the service holds no shared state, so instances are interchangeable, though each has its own key database and you will want to issue the same key on each.
Licensing
LibreTranslate is licensed under the GNU AGPL-3.0. The complete corresponding source of the installed version is shipped on the instance to satisfy AGPL Section 13:
ls /opt/libretranslate/src/libretranslate-1.9.6/LICENSE
/opt/libretranslate/src/libretranslate-1.9.6/LICENSE
The Argos Translate library is separately dual licensed MIT or CC0. The Argos model packages are distributed by the Argos Open Tech project through its package index, which publishes no per package licence field, so no licence claim is made here about the model weights themselves; the manifest on the instance identifies exactly which packages are installed and where they came from.
cloudimg is not affiliated with, endorsed by or sponsored by the LibreTranslate or Argos Open Tech projects.
Support
cloudimg provides 24/7 technical support for this image by email and live chat at support@cloudimg.co.uk. We aim to respond within one business day.
We can help with deployment and initial configuration, retrieving and rotating the per instance API key, issuing additional keys with their own rate limits, adding and loading further language models, replacing the self signed certificate and serving the API on your own domain, rate limit and thread tuning, growing the storage volumes, and sizing guidance for throughput.