Artificial Intelligence (AI) Azure

LanguageTool on Ubuntu 24.04 on Azure User Guide

| Product: LanguageTool on Ubuntu 24.04 LTS on Azure

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.service bound to 127.0.0.1:8081, run as a dedicated non root languagetool user under a hardened systemd unit
  • nginx reverse proxy on port 80 with per VM HTTP Basic Auth, ready for TLS
  • An unauthenticated /healthz endpoint for load balancer and probe checks
  • languagetool-firstboot.service for the first boot per VM password and gate proof
  • A unique per VM password generated on first boot, in a root only 0600 file
  • 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.

Terminal showing the languagetool and nginx services active, LanguageTool bound to loopback 8081 with nginx fronting port 80, and the health endpoint returning ok

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.

Terminal showing the root only credentials file with 0600 permissions and the API rejecting an unauthenticated request with HTTP 401

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.

Terminal showing LanguageTool checking a sentence and returning a JSON matches array that flags the deliberate grammar error with a suggested replacement

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.

Terminal showing LanguageTool auto detecting French text and listing the supported languages with 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 use data for a JSON annotated document)
  • language — a language code (en-US, de-DE, fr, …) or auto to detect it
  • motherTongue — your first language, to catch false friends and interference errors
  • enabledRules / disabledRules — comma separated rule IDs to turn specific checks on or off
  • enabledCategories / disabledCategories — same, at the category level
  • level=picky — enable additional, stricter style checks

Introspection endpoints, all behind the Basic Auth gate:

  • GET /v2/languages — every supported language and its code
  • POST /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.