MAATRIX / Блог / Apache Guacamole на сервере: частые ошибки и решения

Apache Guacamole на сервере: частые ошибки и решения

MAATRIX

Guacamole обещает простую вещь — удалённый рабочий стол по RDP, VNC или SSH прямо в браузере, без клиента и VPN. На практике первое знакомство почти всегда упирается в один и тот же набор проблем: guacd не отвечает, RDP виснет на чёрном экране, соединение обрывается через минуту, а логин с guacadmin выдаёт «Invalid Login» без внятной причины. Ниже — разбор, откуда растут эти ошибки и как их закрыть, с рабочими конфигами Docker Compose и nginx.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Как устроена связка и где чаще всего рвётся

Guacamole — это не одно приложение, а три отдельных компонента, и почти все ошибки — это разрыв связи между ними. Браузер по HTML5 и WebSocket подключается к веб-интерфейсу (guacamole.war в Tomcat), веб-интерфейс передаёт команды нативному демону guacd, а уже guacd умеет говорить по протоколам RDP, VNC и SSH с целевой машиной. Плюс отдельно — база авторизации (по умолчанию файл user-mapping.xml, в проде обычно MySQL или PostgreSQL через расширение).

Отсюда и три класса проблем, которые стоит разделять сразу: разрыв между браузером и Tomcat (обычно WebSocket и обратный прокси), разрыв между Tomcat и guacd (сеть, порт 4822, переменные окружения) и разрыв между guacd и целевой машиной (настройки самого RDP/VNC/SSH-сервера). Держите эту схему в голове при диагностике: ошибка в браузере не значит, что виноват браузер, а «не подключается RDP» не всегда значит, что виноват именно RDP-сервер.

Docker Compose с нуля: guacd + guacamole + MySQL

Проще всего поднять связку в Docker — так меньше шансов упустить переменную окружения. Рабочий минимальный docker-compose.yml:

services:
  guacd:
    image: guacamole/guacd:1.5.5
    restart: unless-stopped
    volumes:
      - ./drive:/drive:rw
      - ./record:/record:rw

  guac-db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: замените_на_свой
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: замените_на_свой
    volumes:
      - guac-db-data:/var/lib/mysql
      - ./initdb:/docker-entrypoint-initdb.d:ro

  guacamole:
    image: guacamole/guacamole:1.5.5
    restart: unless-stopped
    depends_on:
      - guacd
      - guac-db
    environment:
      GUACD_HOSTNAME: guacd
      MYSQL_HOSTNAME: guac-db
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: замените_на_свой
    ports:
      - "127.0.0.1:8080:8080"

volumes:
  guac-db-data:

Ключевой момент, который часто пропускают: MySQL-образу нужна готовая схема, а официальный образ guacamole/guacamole её сам не создаёт. Схему генерирует утилита из отдельного образа, и её нужно один раз выгрузить в каталог initdb:

mkdir -p initdb
docker run --rm guacamole/guacamole:1.5.5 /opt/guacamole/bin/initdb.sh --mysql > initdb/initdb.sql
docker compose up -d

Если поднять контейнеры до того, как файл initdb.sql окажется на месте — MySQL инициализирует пустую базу без таблиц Guacamole, а docker-entrypoint-initdb.d выполняется только при первом запуске на пустом томе. Если это уже произошло, проще всего удалить том guac-db-data и поднять базу заново с готовым дампом, а не чинить схему руками. Общие принципы такого запуска описаны в статье про Docker Compose для продакшена: фиксируйте версии образов тегами, а не latest, чтобы обновление не сломало схему базы под вами.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

«Cannot connect to guacd» и обрывы через несколько секунд

Ошибка Cannot connect to guacd в логах guacamole или в веб-интерфейсе почти всегда означает одно из двух: контейнеры не видят друг друга по сети, либо переменная GUACD_HOSTNAME указывает не туда. В Docker Compose оба сервиса должны быть в одной сети (по умолчанию compose создаёт общую сеть автоматически, если сервисы не разнесены по разным файлам) и обращаться друг к другу по имени сервиса, а не по localhost — это частая ошибка при переносе конфига из инструкции, писавшейся под голую установку без контейнеров:

# проверить, видит ли guacamole контейнер guacd по имени
docker compose exec guacamole ping -c 2 guacd
# проверить, что порт 4822 действительно слушается
docker compose exec guacd sh -c "netstat -tulpn 2>/dev/null || ss -tulpn"

Второй частый источник боли — соединение обрывается через 20-60 секунд без видимой причины, хотя guacd в порядке. Причина почти всегда в обратном прокси перед Guacamole: туннель между браузером и Tomcat идёт через WebSocket, а обычный proxy_pass в nginx без явного апгрейда протокола держит соединение как обычный HTTP-запрос и рвёт его по таймауту. Рабочий блок для nginx:

location /guacamole/ {
    proxy_pass http://127.0.0.1:8080/guacamole/;
    proxy_buffering off;
    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;

    proxy_cookie_path /guacamole/ /;
    access_log off;
}

Без строк proxy_http_version 1.1 и заголовков Upgrade/Connection соединение внешне работает — экран рисуется, — но рвётся на первом же простое или крупном пакете данных (например, при вставке буфера обмена). Общие грабли обратного прокси на nginx разобраны в статье про nginx как reverse-proxy, здесь же нюанс специфичен для WebSocket-трафика.

RDP: чёрный экран, зависание на «Connecting», ошибка безопасности

RDP — самый капризный протокол в Guacamole, потому что несёт три независимых уровня согласования: сетевой, TLS/NLA и собственно рисование экрана. Чёрный экран после успешного логина обычно значит, что RDP-сессия установилась, но клиент не может согласовать security mode. В параметрах подключения (Connections → Edit → Parameters → Security) есть поле security, и значение any — не всегда лучший выбор:

Значение securityКогда использовать
nlaWindows 10/11, Server 2016+ с включённым Network Level Authentication (по умолчанию включён)
tlsRDP с TLS, но без NLA
rdpстарый standard RDP security, для legacy-систем
anyпусть сервер сам выбирает — иногда именно это и ломает согласование

Если экран чёрный или зависает на «Connecting to RDP server», сначала явно укажите nla (для актуальной Windows это правильный выбор), а не полагайтесь на any. Второй частый случай — самоподписанный или недоверенный сертификат на стороне RDP-сервера: включите ignore-cert: true в параметрах, иначе Guacamole тихо оборвёт согласование TLS. Третье — разрешение экрана: если resize-method не задан, а физическое разрешение виртуального дисплея сильно отличается от окна браузера, экран может казаться «замороженным», хотя сессия жива — поставьте resize-method: display-update для динамического ресайза без переподключения.

Отдельно проверьте сетевой уровень: RDP-порт 3389 должен быть доступен именно с машины, где крутится guacd, а не с машины, где открыт браузер, — это разные точки сети, и их легко перепутать при диагностике.

VNC и SSH-подключения: где чаще всего ошибаются

VNC в Guacamole чаще всего ломается на пароле. Если подключение падает с «Authentication failed» при, казалось бы, верном пароле — проверьте, не превышает ли он 8 символов: классический протокол VNC (RFB) отбрасывает всё после восьмого символа без предупреждения, а Guacamole передаёт пароль как есть. Второе — параметр read-only: если случайно включён, курсор и клавиатура не доходят до сервера, хотя картинка транслируется нормально, и это часто принимают за «зависание».

SSH-подключения в Guacamole удобны как замена связке «бастион плюс терминальный клиент» — доступ по SSH прямо из браузера, без установки PuTTY или согласования ключей на рабочей машине. Для входа по ключу, а не по паролю, ключ загружается прямо в параметры подключения:

  • private-key — содержимое приватного ключа целиком (не путь к файлу — именно текст ключа);
  • passphrase — если ключ защищён парольной фразой;
  • username — логин на целевом сервере.

Про сам подход к ключам и типичные ошибки прав на них — в статье про SSH-ключи вместо пароля; там же разбор Permission denied publickey, который иногда путают с проблемой именно Guacamole, хотя причина — в правах на сервере назначения. Если вместо букв на экране появляются «кракозябры» — почти всегда дело в несовпадении кодировки терминала, задайте явно UTF-8 в локали сессии на целевом сервере.

Аутентификация: логин, MySQL-расширение, права

По умолчанию (без базы) Guacamole работает от файла user-mapping.xml с единственным пользователем и паролем, зашитыми в конфиг, — это годится только для теста. С MySQL/PostgreSQL логин guacadmin/guacadmin создаётся автоматически при накатывании схемы (см. раздел про Docker Compose выше) — и это первое, что нужно сменить после первого входа: Preferences → Change Password, иначе связка становится открытой дверью для любого, кто угадает дефолтный пароль.

Если после настройки MySQL веб-интерфейс всё равно предлагает файловую авторизацию или логин guacadmin/guacadmin не срабатывает — проверьте две вещи. Во-первых, расширение guacamole-auth-jdbc-mysql должно физически лежать в GUACAMOLE_HOME/extensions/ — в официальном Docker-образе это делается автоматически через переменные MYSQL_*, но при установке без Docker его нужно скопировать руками. Во-вторых, Invalid Login вместо Login failed в логе Tomcat часто значит, что Guacamole не смог достучаться до самой базы — проверьте те же сетевые моменты, что и для guacd:

# логи веб-приложения, где видна причина ошибки авторизации
docker compose logs guacamole | grep -i -E "auth|mysql|jdbc"

Безопасность гейтвея: доступ снаружи, TOTP, ограничение по IP

Guacamole — это единая точка входа сразу ко всей внутренней инфраструктуре: если её пробьют, у атакующего оказывается доступ ко всем сохранённым подключениям сразу. Поэтому три вещи стоит сделать до того, как открывать сервис наружу, а не после инцидента.

Во-первых, TLS обязателен — Guacamole сам по себе HTTP, и без сертификата логин и весь трафик сессии идут в открытом виде. Проще всего закрыть это связкой nginx плюс Let's Encrypt — конфиг с обратным прокси выше уже рассчитан на такую схему.

Во-вторых, включите двухфакторную аутентификацию через расширение guacamole-auth-totp (кладётся в тот же каталог extensions/, что и MySQL-расширение) — без неё утечка одного пароля даёт полный доступ ко всем машинам сразу. Общий подход к TOTP для административных панелей разобран в статье про 2FA для панелей управления — Guacamole в этом смысле ничем не отличается от любой другой панели.

В-третьих, ограничьте доступ к порту прокси по IP или хотя бы поставьте fail2ban на nginx-логи — Guacamole не имеет встроенной защиты от подбора пароля, и открытый наружу логин рано или поздно попадёт под перебор ботами.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Почему после установки написано «guacamole.war not found» или интерфейс не открывается?

В официальном Docker-образе .war уже встроен в Tomcat, и такая ошибка чаще возникает при ручной установке, когда guacamole.war не скопирован в каталог webapps Tomcat или сервис Tomcat не был перезапущен после копирования. Проверьте, что файл лежит по пути $CATALINA_HOME/webapps/guacamole.war и что процесс Tomcat реально перезапущен, а не просто перечитал конфиг.

Подключение работает локально, но не работает через домен с HTTPS — в чём разница?

Почти всегда в обратном прокси: без апгрейда WebSocket (proxy_http_version 1.1 и заголовки Upgrade/Connection, см. раздел выше) экран может даже отрисоваться один раз, но интерактивность и стабильность соединения пропадают именно на внешнем адресе через прокси.

Как поменять пароль guacadmin, если не можешь зайти в интерфейс?

Через прямой SQL-запрос к базе MySQL/PostgreSQL, обновив хэш пароля в таблице guacamole_user (хэш и соль генерируются определённым алгоритмом, вручную текстом пароль не пишется) — проще всего временно выполнить это через дамп и накат исправленной записи. Либо, если тестовая среда, удалить том базы и поднять схему заново — тогда guacadmin/guacadmin вернётся к дефолту.

Можно ли подключаться к нескольким серверам через один экземпляр Guacamole?

Да, это основной сценарий использования — один guacd умеет обслуживать множество одновременных подключений к разным целевым машинам по RDP, VNC и SSH, каждое описывается отдельной записью в веб-интерфейсе со своими параметрами и правами доступа для конкретных пользователей или групп.

Нужен ли открытый порт RDP/VNC наружу, если стоит Guacamole?

Нет и не должен быть — в этом смысл связки: только guacd обращается к целевым машинам по RDP/VNC/SSH, обычно внутри той же приватной сети или через VPN, а наружу торчит один HTTPS-порт самого Guacamole. Закрытые снаружи 3389 и 5900 существенно снижают поверхность атаки.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →