RE:NODE

Базы данных13 мин чтения

Удалённое подключение к MySQL: хост, порт, TLS и клиенты

Как подключиться к удалённому MySQL 8.4 из клиента mysql, приложения и GUI-инструментов: хост и порт, ssl-mode, caching_sha2_password и типичные ошибки.

0 прочтений

Чтобы подключиться к удалённому серверу MySQL, нужны пять вещей: имя хоста или IP-адрес, порт, имя пользователя, его пароль и имя базы данных. С ними mysql -h host -P port -u user -p database даёт вам приглашение командной строки, а те же пять значений составляют строку подключения для вашего приложения. Большинство сбоев происходит в одном из трёх мест: сеть (порт неверный или недоступен), учётная запись (пользователь существует для другого шаблона хоста, чем тот, с которого вы подключаетесь) или плагин аутентификации (старый клиент, который не понимает caching_sha2_password, а MySQL 8.4 использует его по умолчанию и при первом входе требует зашифрованного соединения).

Это руководство проходит по каждой части в том порядке, в каком вы с ними сталкиваетесь: нужные данные, клиент командной строки, шифрование и --ssl-mode, плагин аутентификации, строки подключения для распространённых сред выполнения, графические инструменты и сообщения об ошибках, которые люди действительно видят. Всё здесь относится к MySQL 8.4 LTS, с пометками там, где 8.0 ведёт себя иначе.

Пять параметров и откуда они берутся#

ПараметрПримерПримечания
Хостdb.example.net или 203.0.113.20Имя или адрес. С другой машины - никогда не localhost
Порт3306 или тот, что указан в тарифе3306 - лишь значение по умолчанию. Хостинг часто использует другой
ПользовательappУчётная запись - это имя пользователя плюс шаблон хоста
ПарольсгенерированныйХраните его в переменной окружения, а не в коде
База данныхappdbНеобязательна при подключении, но приложение должно её указывать

На вашей собственной машине MySQL слушает 3306, а учётная запись, под которой вы работаете, часто root@localhost. На хостинговой базе данных ничего из этого нельзя считать само собой разумеющимся. Хост - это тот адрес, который даёт провайдер, порт может быть любым, а учётная запись для повседневной работы - пользователь приложения с правами на одну базу данных, а не root.

На RE:NODE сервер MySQL создаётся с паролем root, базой данных приложения и пользователем приложения, всё сгенерировано, а доступен он по хосту и порту тарифа, которые показаны в панели. Именно эти два значения и нужно копировать: не угадывайте 3306. На тарифах баз данных нет слота прокси, поэтому к серверу подключаются напрямую по этому адресу и порту, а не через домен и сертификат, которыми управляет панель.

Одно правило избавит от массы путаницы потом: localhost в MySQL - особый случай. Когда клиенту передают localhost, он подключается через файл Unix-сокета, а не по TCP, и полностью игнорирует порт. Если вы на той же машине и хотите TCP, используйте 127.0.0.1. С другой машины localhost просто означает ваш собственный компьютер.

Подключение через клиент командной строки mysql#

Клиент mysql входит в каждый пакет сервера MySQL и в пакеты только с клиентом (mysql-client в Debian и Ubuntu, mysql в Homebrew, MySQL Installer или ZIP-архив в Windows). Используйте клиент серии 8.x или новее. Клиенты старше 8.0 не могут пройти аутентификацию в учётной записи 8.4 с настройками по умолчанию - об этом ниже.

bash
$ mysql -h db.example.net -P 30412 -u app -p appdbEnter password:Welcome to the MySQL monitor.  Commands end with ; or \g.Server version: 8.4.6 MySQL Community Server - GPLmysql>

-p без ничего после него заставляет клиент спросить пароль, и тогда он не попадает ни в историю шелла, ни в список процессов. -pSecret (без пробела) работает, но оставляет пароль в ~/.bash_history. -p Secret (с пробелом) делает не то, что кажется: Secret воспринимается как имя базы данных, а пароль у вас всё равно спросят.

Войдя, проверьте, куда вы попали:

sql
SELECT CURRENT_USER(), USER(), DATABASE(), @@version, @@port;

USER() - это имя и хост, которые вы заявили; CURRENT_USER() - учётная запись, которую MySQL на самом деле сопоставил, и именно её привилегии действуют. Если они неожиданно различаются - вы подключились как app, но сопоставились с ''@'%', анонимной учётной записью, - вы нашли причину, по которой ваши GRANT как будто не работают. \s (или status) выводит сводку соединения, в том числе зашифровано ли оно:

code
SSL:                    Cipher in use is TLS_AES_256_GCM_SHA384Connection:             db.example.net via TCP/IPServer version:         8.4.6 MySQL Community Server - GPLProtocol version:       10

Учётные данные в файле опций

Набирать хост и порт каждый раз быстро надоедает. Клиент читает файлы опций, и группа [client] в ~/.my.cnf (или %APPDATA%\MySQL\.mylogin.cnf через инструмент ниже) задаёт значения по умолчанию:

~/.my.cnf
[client]host=db.example.netport=30412user=appssl-mode=REQUIRED

Не кладите пароль в обычный файл и дайте клиенту его спросить, либо используйте mysql_config_editor, который пишет обфусцированный .mylogin.cnf:

bash
$ mysql_config_editor set --login-path=prod --host=db.example.net \    --port=30412 --user=app --password$ mysql --login-path=prod appdb

Обфускация защищает от случайного взгляда, а не от настойчивого читателя. Поставьте chmod 600 на любой из этих файлов. Клиент отказывается читать файл опций, доступный на запись всем, и прямо об этом сообщает.

Шифрование: ssl-mode и что означает каждое значение#

Соединение MySQL либо зашифровано TLS, либо нет, а насколько настойчиво этого требовать, решает клиент через --ssl-mode. По умолчанию стоит PREFERRED: шифровать, если сервер это предлагает, и молча откатываться на открытый текст, если нет.

--ssl-modeШифруетПроверяет сертификатКогда использовать
DISABLEDНетНетНикогда через интернет
PREFERREDЕсли предложеноНетПо умолчанию; при сбое пропускает
REQUIREDДа, или ошибкаНетСамоподписанный сертификат сервера
VERIFY_CAДаПодписан вашим CAУ вас есть файл CA
VERIFY_IDENTITYДаCA и имя хостаПубличный CA, имя совпадает

MySQL 8.x при первом запуске генерирует самоподписанный сертификат и ключ в каталоге данных, если оператор это не отключил, поэтому большинство серверов предлагают TLS из коробки. Самоподписанный сертификат шифрует соединение, но ничего не доказывает о том, кто на другом конце, поэтому VERIFY_CA и VERIFY_IDENTITY с ним не пройдут, если только вам не дали файл CA и вы не передали его через --ssl-ca=ca.pem.

Для базы данных, к которой подключаются через публичный интернет, ставьте как минимум REQUIRED. На современном железе это не стоит ничего измеримого и закрывает случай, когда неправильно настроенный сервер молча понижает вас до открытого текста. Затем проверьте через \s или из SQL:

sql
SHOW SESSION STATUS LIKE 'Ssl_version';SHOW SESSION STATUS LIKE 'Ssl_cipher';

Пустой Ssl_cipher означает, что сессия не зашифрована. MySQL 8.4 по умолчанию принимает только TLS 1.2 и 1.3, а старый серверный переключатель --ssl и переменную have_ssl в нём убрали; если руководство велит проверить have_ssl, оно написано для 5.7 или 8.0.

Сервер тоже может настаивать. REQUIRE SSL на учётной записи (ALTER USER 'app'@'%' REQUIRE SSL;) запрещает этому пользователю любое незашифрованное соединение, а require_secure_transport=ON запрещает его всем. Обе вещи полезно знать, если у вас есть учётная запись root.

caching_sha2_password и проблема первого подключения#

Начиная с MySQL 8.0 плагин аутентификации по умолчанию - caching_sha2_password. В MySQL 8.4 старый плагин mysql_native_password не просто вышел из моды - он отключён по умолчанию, и его нужно включить через mysql_native_password=ON в конфигурации сервера, прежде чем сможет войти хоть одна учётная запись, которая его использует. В MySQL 9.0 его удалили совсем. Рассчитывайте на caching_sha2_password везде.

У плагина есть одна особенность, которая порождает большую часть непонятных ошибок. Когда пользователь впервые проходит аутентификацию после перезапуска сервера (или после смены пароля), у сервера нет кэшированного хэша для этого пользователя, и чтобы его вычислить, пароль нужно передать безопасно. «Безопасно» означает одно из двух:

  1. Соединение зашифровано TLS, и тогда пароль идёт внутри туннеля, или
  2. Клиент получает публичный RSA-ключ сервера и шифрует пароль им.

Если ни то ни другое не выполняется, вход завершается ошибкой:

code
ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reportederror: Authentication requires secure connection.

Лечение - использовать TLS, что вам в любом случае стоит делать. Если не получается, клиент может запросить ключ через --get-server-public-key (командная строка), allowPublicKeyRetrieval=true (JDBC) или аналогичную опцию в вашем драйвере. Запрос ключа по незашифрованному соединению открыт для атаки «человек посередине», которая подменит ключ, и именно поэтому по умолчанию это выключено.

Другая ошибка - удел старых клиентов и старых драйверов:

code
Authentication plugin 'caching_sha2_password' cannot be loaded

Это клиент эпохи 5.7, старый PHP mysqlnd или библиотека вроде исходного пакета Node mysql (не mysql2), которая так и не научилась этому плагину. Обновите клиент или драйвер. Пересоздание пользователя с mysql_native_password сработает, только если на сервере этот плагин включён, а в 8.4 он выключен, пока кто-то его не включит, - исправить клиент выгоднее. Полный список изменений аутентификации есть в статье MySQL 8.4 LTS: что изменилось.

Строки подключения для распространённых сред выполнения#

Каждому драйверу нужны те же пять значений, просто расставленные по-разному. Храните их в переменных окружения и собирайте строку во время выполнения; как это делать, описано в статье переменные окружения и секреты.

.env
DB_HOST=db.example.netDB_PORT=30412DB_USER=appDB_PASSWORD=change-meDB_NAME=appdbDATABASE_URL=mysql://app:change-me@db.example.net:30412/appdb

URL ломается, если в пароле есть @, :, / или #, потому что это синтаксис URL. Либо закодируйте их процентной кодировкой (@ превращается в %40), либо передавайте части по отдельности, что позволяет большинство драйверов.

Node.js with mysql2
import mysql from "mysql2/promise";const pool = mysql.createPool({  host: process.env.DB_HOST,  port: Number(process.env.DB_PORT),  user: process.env.DB_USER,  password: process.env.DB_PASSWORD,  database: process.env.DB_NAME,  connectionLimit: 10,  ssl: { rejectUnauthorized: false }, // TLS on, self-signed certificate accepted});
Python with PyMySQL / SQLAlchemy
from sqlalchemy import create_engineimport osurl = (f"mysql+pymysql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}"       f"@{os.environ['DB_HOST']}:{os.environ['DB_PORT']}/{os.environ['DB_NAME']}"       "?charset=utf8mb4")engine = create_engine(url, pool_size=5, pool_recycle=1800,                       # TLS on; no CA given, so a self-signed cert is accepted                       connect_args={"ssl": {"check_hostname": False}})
PHP with PDO
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4',    getenv('DB_HOST'), getenv('DB_PORT'), getenv('DB_NAME'));$pdo = new PDO($dsn, getenv('DB_USER'), getenv('DB_PASSWORD'), [    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,]);
JDBC (MySQL Connector/J 8.x and 9.x)
jdbc:mysql://db.example.net:30412/appdb?sslMode=REQUIRED&characterEncoding=UTF-8

Две настройки должны быть в каждой строке. Кодировка должна быть utf8mb4, иначе однажды ваше приложение сохранит эмодзи как четыре вопросительных знака - смотрите utf8mb4 и правила сравнения. И что-то должно пересоздавать простаивающие соединения раньше, чем их закроет серверный wait_timeout (по умолчанию 28800 секунд, восемь часов), иначе первый запрос после тихой ночи упадёт с «MySQL server has gone away». Этим занимаются pool_recycle в SQLAlchemy и таймаут простоя пула в других драйверах. Каким должен быть размер каждого пула - отдельная тема: лимиты соединений MySQL и пулы.

Если говорить именно о PHP, статья PHP PDO и MySQL подробнее разбирает подготовленные запросы и режимы ошибок, а сторона C# - MySqlConnector против Connector/NET от Oracle - описана в статье .NET с PostgreSQL, MySQL или SQL Server.

GUI-инструменты: Workbench, DBeaver, HeidiSQL и другие#

Подойдёт любой из распространённых графических клиентов, и все они спрашивают одни и те же поля. Различается лишь то, где они прячут опции TLS и публичного ключа.

  • MySQL Workbench (Oracle, бесплатный): Connection Method Standard (TCP/IP), хост, порт, пользователь. На вкладке SSL выставьте Use SSL в Require. Workbench ближе всех к собственным возможностям сервера, включая визуальный EXPLAIN.
  • DBeaver (бесплатная community-редакция): новое подключение MySQL, заполните Server Host и Port. На вкладке Driver properties выставьте allowPublicKeyRetrieval в true, если вы не используете TLS и получили ошибку публичного ключа; на вкладке SSL отметьте Use SSL, а для самоподписанного сертификата снимите галочку проверки сертификата.
  • HeidiSQL (Windows, бесплатный): Network type MariaDB or MySQL (TCP/IP), затем вкладка SSL. Лёгкий и быстрый, вполне годится для просмотра и быстрых правок.
  • TablePlus, DataGrip, Beekeeper Studio: те же поля, TLS на вкладке SSL или Advanced.

Общая для всех ловушка - версия встроенного драйвера. Инструмент со старым коннектором может падать с ошибкой плагина, описанной выше, хотя клиент командной строки работает. Это исправляет обновление инструмента или драйвера внутри него. Статья сравнение GUI-клиентов MySQL разбирает каждый подробнее.

Когда подключение не удаётся: расшифровка ошибок#

code
ERROR 2003 (HY000): Can't connect to MySQL server on 'db.example.net:3306' (110)

Сетевой сбой ещё до того, как дело дошло до MySQL. Ошибка 110 - это таймаут (что-то отбрасывает пакеты: файрвол, неверный адрес), 111 - «connection refused» (адрес верный, но на этом порту никто не слушает). Сначала проверьте порт - 3306 в сообщении выдаёт, что клиент взял значение по умолчанию, потому что вы забыли -P. Затем проверьте, что ваша собственная сеть разрешает исходящие соединения на этот порт; некоторые офисные и университетские сети блокируют всё, кроме веб-трафика. nc -vz db.example.net 30412 или Test-NetConnection db.example.net -Port 30412 в Windows покажет, отвечает ли порт вообще.

code
ERROR 1045 (28000): Access denied for user 'app'@'198.51.100.7' (using password: YES)

До MySQL дошли, и он отклонил вход. Либо пароль неверный, либо нет учётной записи app, чей шаблон хоста совпадает с вашим адресом 198.51.100.7. В сообщении указан хост, который увидел MySQL, и совпадать должен именно он. using password: NO означает, что клиент вообще не отправил пароль - обычно пропущен -p или переменная окружения пуста. Как сопоставляются шаблоны хостов, объясняет статья пользователи и привилегии MySQL.

code
ERROR 1044 (42000): Access denied for user 'app'@'%' to database 'other'

Вы вошли нормально, но запросили базу данных, на которую у учётной записи нет прав. Подключитесь к нужной базе или попросите GRANT.

code
ERROR 1129 (HY000): Host '198.51.100.7' is blocked because of many connection errors

С вашего адреса было больше неудачных рукопожатий, чем разрешает max_connect_errors (по умолчанию 100). Кто-то с правами администратора снимает блокировку через TRUNCATE TABLE performance_schema.host_cache;. В MySQL 8.4 FLUSH HOSTS больше нет; замена ему - этот TRUNCATE (или mysqladmin flush-hosts). Затем найдите то, что давало сбои, - обычно это проверка здоровья, которая открывает TCP-соединение и закрывает его, не входя.

code
ERROR 1040 (HY000): Too many connections

Сервер упёрся в max_connections (по умолчанию 151). Что-то теряет соединения, или пул больше, чем сервер способен выдержать. Повышение лимита редко бывает решением; почему - объясняет статья пулы соединений и лимиты.

code
ERROR 2013 (HY000): Lost connection to MySQL server during queryERROR 2006 (HY000): MySQL server has gone away

Соединение умерло. Обычные причины: сервер закрыл простаивающее соединение после wait_timeout, отдельный оператор превысил max_allowed_packet (в 8.x по умолчанию 64 МБ), сервер перезапустился или сетевое устройство посередине убило долго простаивавшую TCP-сессию. Пересоздавайте соединения пула чаще, чем истекает таймаут, и переподключайтесь при этой ошибке.

Как обезопасить удалённую базу данных#

База данных, доступная из интернета, становится мишенью в тот же момент, как появляется. Сканеры весь день перебирают root с распространёнными паролями на каждом адресе и порту, который находят. Защита несложная:

  • Для приложения используйте пользователя приложения, а не root. Root оставьте для администрирования и дайте ему длинный сгенерированный пароль.
  • Требуйте TLS для всего, что идёт через интернет.
  • Дайте каждому приложению собственного пользователя с правами только на его базу данных, чтобы утечка одних учётных данных не открывала остальное.
  • Меняйте пароль в тот момент, когда он мог утечь (закоммиченный .env, скриншот в переписке с поддержкой), - MySQL 8 поддерживает двойные пароли через RETAIN CURRENT PASSWORD, так что менять можно без простоя.
  • Следите за неудачными входами. performance_schema.host_cache считает ошибки по каждому адресу.

Остальное описано в статье чек-лист безопасности базы данных, включая то, что делать, если вы подозреваете, что учётными данными воспользовался кто-то другой.

FAQ#

Какой порт использует MySQL?

3306 по умолчанию и 33060 для X Protocol, который использует хранилище документов MySQL Shell. Хостинговый сервер может слушать любой порт, поэтому используйте тот, что дал провайдер, а не предполагайте значение по умолчанию. Если вы забудете -P, клиент молча возьмёт 3306.

Почему localhost работает на сервере, но не с моего ноутбука?

Потому что localhost означает «эта машина», а в MySQL ещё и «использовать Unix-сокет». С вашего ноутбука localhost - это ваш ноутбук. Используйте имя хоста или адрес сервера и убедитесь, что существует учётная запись с шаблоном хоста, который совпадает с местом, откуда вы подключаетесь.

Нужен ли TLS, если пароль надёжный?

Да. Без TLS каждый запрос и каждый результат идут по сети открытым текстом, а caching_sha2_password приходится откатываться на обмен RSA-ключом при первом входе после перезапуска. Выставьте --ssl-mode=REQUIRED или аналог в вашем драйвере.

Как исправить «Authentication plugin caching_sha2_password cannot be loaded»?

Обновите клиент или драйвер до того, что поддерживает аутентификацию MySQL 8: клиент mysql версии 8.x или новее, mysql2 вместо mysql в Node, актуальный PHP, актуальный Connector/J. Перевод учётной записи на mysql_native_password сработает, только если на сервере 8.4 этот плагин включён, а по умолчанию он выключен.

Можно ли подключиться, не указывая базу данных?

Да. Не указывайте её в командной строке и выполните USE appdb; после входа или добавляйте имя базы данных перед именами таблиц. Приложения же должны указывать базу данных в подключении, чтобы каждый запрос выполнялся там, где вы ожидаете.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000