LanguageTool on Ubuntu 24.04 on Azure User Guide
Overview
LanguageTool is an open source proofreading engine that checks grammar, style and spelling in more than twenty languages. Send it a block of text and it returns a structured list of issues — each with the rule that fired, a human readable message, the offending span and suggested replacements. It combines a large library of hand written linguistic rules with statistical and machine learning models, and because it runs entirely on your own machine, the text you check never leaves your infrastructure. That makes it a natural building block for writing assistants, editors, content and documentation pipelines, and any application that wants proofreading without sending user text to a third party.
The cloudimg image installs the official LanguageTool 6.6 standalone distribution at /opt/languagetool, running as the languagetool service under OpenJDK 17 bound to loopback 127.0.0.1:8081, behind nginx on port 80.
Secure by default, no default login: the LanguageTool server ships with no authentication, and its /v2/check endpoint is CPU intensive and abusable if the port is exposed. This image never exposes it. The server binds loopback only (the public mode that opens it to the network requires an explicit flag that is never passed), and nginx is the only public listener, fronting the whole REST surface with HTTP Basic Auth. A languagetool-firstboot.service oneshot generates a unique per VM password on each VM's first boot, writes a bcrypt .htpasswd and a root only info note, proves the auth gate with a real grammar check round trip, then disables itself. No two VMs share a password and none is baked into the image.
Note on the interface: LanguageTool Server is an API first service with no bundled web interface. You drive it through its REST API, the official LanguageTool clients and browser add ons (pointed at your server), or your own application. This guide uses curl against the API.
What is included:
- LanguageTool 6.6 (
languagetool-server) on OpenJDK 17 languagetool.servicebound to127.0.0.1:8081, run as a dedicated non rootlanguagetooluser under a hardened systemd unit- nginx reverse proxy on port 80 with per VM HTTP Basic Auth, ready for TLS
- An unauthenticated
/healthzendpoint for load balancer and probe checks languagetool-firstboot.servicefor the first boot per VM password and gate proof- A unique per VM password generated on first boot, in a root only
0600file - All base language models bundled in the distribution, so checking works immediately
- Ubuntu 24.04 LTS base, fully patched
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet. LanguageTool is CPU and memory bound while checking; the JVM heap is sized for the recommended VM. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) for evaluation and light workloads, or a Standard_D2s_v5 or larger for production throughput and higher concurrency.
Step 1: Deploy from the Azure Portal
Search the Marketplace for LanguageTool on Ubuntu 24.04, choose your VM size, and attach an NSG that allows TCP 22 (SSH) from your management network and TCP 80 / 443 (the API) from the networks and applications that need to submit text. Front port 80 with TLS in production (see the HTTPS section below).
Step 2: Deploy from the Azure CLI
RG="languagetool-prod"; LOCATION="eastus"; VM_NAME="languagetool-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/languagetool-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name lt-vnet --address-prefix 10.90.0.0/16 --subnet-name lt-subnet --subnet-prefix 10.90.1.0/24
az network nsg create -g "$RG" --name lt-nsg
az network nsg rule create -g "$RG" --nsg-name lt-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name lt-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 443 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name lt-vnet --subnet lt-subnet --nsg lt-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 4: Verify the services are running
The LanguageTool server listens on loopback 127.0.0.1:8081 and nginx fronts the API on port 80. The /healthz endpoint is public; every other endpoint requires your per VM password.
sudo systemctl is-active languagetool nginx
sudo ss -tln | grep -E ':80 |:8081'
curl -s http://127.0.0.1/healthz
You should see both services active, the two listening sockets, and ok from the health endpoint.

Step 5: Read your per VM password
The password was generated on this VM's first boot and stored in a root only file. Read it and keep it secret: it authenticates every API request. Confirm that unauthenticated requests are rejected.
sudo ls -l /root/languagetool-credentials.txt
echo "unauthenticated /v2/languages -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/v2/languages)"
The info note holds LT_USERNAME (admin) and LT_PASSWORD, is owned root:root with mode 0600, and an unauthenticated call to /v2/languages returns 401 because the nginx Basic Auth gate enforces authentication.

Step 6: Check some text
This is what LanguageTool does: find issues in text. Load the per VM password into a shell variable, then POST text to the /v2/check endpoint. Pass the text and the language code as form fields; the response is a JSON matches array, one entry per issue.
export LT_PW=$(sudo grep '^LT_PASSWORD=' /root/languagetool-credentials.txt | cut -d= -f2-)
# A sentence with a deliberate agreement error
curl -s -u "admin:$LT_PW" \
--data-urlencode 'text=This are a test of the LanguageTool server.' \
--data-urlencode 'language=en-US' \
http://127.0.0.1/v2/check | python3 -m json.tool
The response lists each issue in matches: the agreement error on "This are" appears with a message, the character offset and length, and a set of suggested replacements. Point your own text at the same endpoint to check it.

Step 7: Automatic language detection and other languages
Omit the language field (or set language=auto) to let LanguageTool detect the language automatically, and use any supported language code such as de-DE, fr or es.
export LT_PW=$(sudo grep '^LT_PASSWORD=' /root/languagetool-credentials.txt | cut -d= -f2-)
# Auto detect the language
curl -s -u "admin:$LT_PW" \
--data-urlencode 'text=Ceci est une phrase avec une faute daccord.' \
--data-urlencode 'language=auto' \
http://127.0.0.1/v2/check | python3 -c 'import sys,json; d=json.load(sys.stdin); print("detected:", d["language"]["detectedLanguage"]["name"]); print("issues:", len(d["matches"]))'
# List every supported language and its code
curl -s -u "admin:$LT_PW" http://127.0.0.1/v2/languages | python3 -c 'import sys,json; d=json.load(sys.stdin); print(len(d), "languages"); [print(" ", x["name"], x["longCode"]) for x in d[:8]]'
/v2/check reports the detected language when you use auto, and /v2/languages returns the full catalogue of supported languages and their codes.

Step 8: The check API and its options
You drive everything through POST /v2/check. The most useful fields:
text— the plain text to check (or usedatafor a JSON annotated document)language— a language code (en-US,de-DE,fr, …) orautoto detect itmotherTongue— your first language, to catch false friends and interference errorsenabledRules/disabledRules— comma separated rule IDs to turn specific checks on or offenabledCategories/disabledCategories— same, at the category levellevel=picky— enable additional, stricter style checks
Introspection endpoints, all behind the Basic Auth gate:
GET /v2/languages— every supported language and its codePOST /v2/check— the checking endpoint itself
For example, run a stricter "picky" check:
export LT_PW=$(sudo grep '^LT_PASSWORD=' /root/languagetool-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$LT_PW" \
--data-urlencode 'text=The report was authored by the team in a very unique way.' \
--data-urlencode 'language=en-US' --data-urlencode 'level=picky' \
http://127.0.0.1/v2/check | python3 -c 'import sys,json; d=json.load(sys.stdin); print("issues:", len(d["matches"])); [print(" -", m["rule"]["id"], ":", m["message"]) for m in d["matches"]]'
Optional: add n-gram data for fewer false positives
LanguageTool can use large n-gram data sets to catch confused words (for example their vs there) with fewer false positives. These data sets are several gigabytes per language and are not bundled, to keep the image small. To add them, download the language pack from LanguageTool, extract it to a directory, and point the server at it by adding languageModel=/opt/languagetool/ngrams to /opt/languagetool/server.properties, then sudo systemctl restart languagetool. See the LanguageTool documentation for the current download location and disk requirements.
Step 9: Enable HTTPS
nginx fronts the API on port 80 and is ready for TLS. Point a DNS record at the VM, then obtain a certificate with certbot and let it configure nginx for port 443.
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com
After issuance, restrict the NSG so only 443 (and 22 from your management network) is reachable, and submit text over https://your-domain.example.com/v2/check.
Persistence, first boot and updates
LanguageTool Server is stateless — it keeps no data between requests, so there is no data volume to manage. The distribution and all bundled language models live on the OS disk and are captured into the image, so a VM launched from this image is ready immediately. The first boot service generates the per VM Basic Auth password, proves the auth gate, then disables itself so subsequent reboots are unaffected. The OS ships fully patched with unattended security upgrades enabled. To change the password later, run sudo htpasswd -B /etc/nginx/.languagetool.htpasswd admin and reload nginx with sudo systemctl reload nginx.
Support
Every cloudimg deployment includes 24/7 support with a 24 hour response SLA. Contact support@cloudimg.co.uk for help with deployment, configuration or scaling.
This is a repackaged open source software product with additional charges for cloudimg support services. LanguageTool is licensed under the GNU Lesser General Public License (LGPL). All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.