Как установить MySQL на Ubuntu: безопасная настройка, удалённый доступ и резервное копирование 

Валерий Волков

Время прочтения 25 минут

MySQL на Ubuntu: от пустого сервера до рабочей базы

Добро пожаловать, дорогой читатель!

Сегодня разберем одну из самых распространенных задач при подготовке серверного приложения — установку и базовую настройку MySQL на Ubuntu.

Представим довольно обычную ситуацию: у нас есть VPS, на нем скоро будет работать сайт, API, интернет-магазин или другой сервис, которому нужно где-то хранить данные.

Это могут быть:

  • Учетные записи пользователей;
  • Заказы;
  • Настройки;
  • Сообщения;
  • Товары;
  • История операций.

Для всего этого приложению нужна база данных.

В нашем случае эту роль будет выполнять MySQL Server.

Но просто установить пакет и увидеть статус active недостаточно. База данных — это уже не тестовый сервис, который можно безболезненно удалить и поднять заново.

Если здесь ошибиться с доступом или резервными копиями, последствия будут заметно неприятнее.

Поэтому пойдем не по схеме:

а немного дальше:

То есть в конце у нас должна получиться не просто установленная СУБД, а база, которую уже можно использовать как основу для реального приложения.

Почему одной установки нам мало

Установить MySQL на Ubuntu действительно несложно.

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

Но тут возникает важный вопрос: кто именно сможет к нему подключаться и что этот пользователь сможет делать?

Для тестового стенда, в изолированной сети, можно было бы выдать одному аккаунту все права и на этом закончить.

Для реального проекта такой подход не подходит.

Допустим, нашему приложению нужна только одна база: shop_db

Логично создать для него отдельного пользователя, например: shop_app

и разрешить ему работать именно с этой базой.

Тогда это выглядит так:

Разница примерно как между выдачей сотруднику ключа от его кабинета и связки ключей от всего здания.

Оба варианта позволят ему попасть на работу, но во втором случае прав у него явно больше, чем необходимо.

С базами данных действует тот же принцип: пользователь должен получать только тот доступ, который действительно нужен его приложению.

Отдельная история — удаленное подключение.

Иногда к MySQL нужно обращаться не только с самого VPS, но и с другого сервера, административной машины или backend-сервиса.

Можно было бы просто открыть 3306 для всего интернета.

Технически это работает.

Практически — лучше так не делать.

Если удаленный доступ действительно нужен, мы ограничим его сразу на нескольких уровнях:

И наконец, даже идеально настроенные права не спасают от случайного удаления таблицы, неудачного обновления приложения или человеческой ошибки.

Поэтому нам понадобится еще один обязательный элемент — резервная копия.

К чему мы должны прийти в конце

К завершению всей работы  у нас будет работающий MySQL Server с понятной и контролируемой схемой доступа.

Условно:

При этом сам сервер мы не будем оставлять в состоянии «кто угодно может попробовать подключиться».

Дополнительно создадим резервную копию через mysqldump и обязательно проверим обратную операцию — восстановление.

Это важно.

В конце цепочка будет выглядеть уже так:

В результате мы пройдем весь базовый жизненный цикл: от пустого Ubuntu-сервера до MySQL, к которому приложение может подключаться с нужными правами и который при необходимости можно восстановить из резервной копии.

А начнем с самого фундамента — проверим Ubuntu и установим MySQL Server.

Готовим Ubuntu и устанавливаем MySQL Server

Проверяем систему

Для начала посмотрим версию Ubuntu: cat /etc/os-release

В выводе будут строки вроде:

NAME="Ubuntu"

VERSION="24.04 LTS (Noble Numbat)"

VERSION_ID="24.04"

Если используется Ubuntu 26.04 LTS, соответствующие значения для этой версии.

Эта проверка помогает понимать, с какой системой мы работаем и из какого репозитория будут устанавливаться пакеты.

Заодно можно проверить архитектуру: dpkg --print-architecture

На большинстве обычных VPS результат будет: amd64

Теперь посмотрим, хватает ли серверу места и памяти хотя бы для базовой установки:

df -h /

free -h

Для небольшого тестового стенда MySQL не требует огромных ресурсов, но реальная нагрузка уже будет зависеть от размера базы, количества запросов, настроек и самого приложения.

На этом этапе нам важно другое: система должна быть в рабочем состоянии, а диск — не забит под завязку.

Если всё выглядит нормально, можно переходить к установке.

Устанавливаем MySQL через APT

Сначала обновим индекс пакетов: sudo apt update

APT получит свежую информацию о доступных версиях программ из подключенных репозиториев Ubuntu.

Теперь установим MySQL Server: sudo apt install -y mysql-server

Параметр -y автоматически подтверждает установку.

APT скачает сам MySQL Server, необходимые зависимости и добавит системный сервис, через который база будет запускаться в Ubuntu.

Подготовил схему того, что вообще происходит:

После завершения установки база уже физически находится на сервере.

А теперь убедимся, что сервис точно запустился.

Проверяем статус сервиса

Проверим MySQL через systemd: sudo systemctl status mysql --no-pager

Нас интересует строка: Active: active (running)

Она означает, что процесс MySQL запущен и готов принимать локальные подключения.

Для короткой проверки можно использовать: systemctl is-active mysql

Ожидаемый ответ: active

Заодно посмотрим, включен ли сервис в автозапуск: systemctl is-enabled mysql

В нормальной ситуации увидим: enabled

То есть после перезагрузки VPS MySQL запустится автоматически.

Если сервис почему-то не поднялся, сначала попробуйте: sudo systemctl start mysql

А если он находится в состоянии failed, посмотрим журнал: sudo journalctl -u mysql --no-pager -n 50

Именно там обычно можно увидеть причину, по которой сервер базы данных не смог стартовать.

Чаще всего проблема оказывается одной из довольно приземленных:

  • Ошибка в конфигурации MySQL — например, после ручного изменения mysqld.cnf;
  • Порт 3306 уже занят другим процессом;
  • На диске закончилось свободное место;
  • MySQL не может получить доступ к своим файлам из-за неправильных прав;
  • После неудачного обновления остались проблемы с пакетами или зависимостями.

Например, если MySQL сообщает, что не может занять порт 3306, можно проверить, кто уже его использует: sudo ss -lntp | grep 3306

А свободное место на диске проверяется уже знакомой командой: df -h /

Сам журнал в такой ситуации работает как подсказка: вместо того чтобы наугад перезапускать сервис и менять настройки, сначала смотрим конкретную ошибку, а уже потом исправляем ее причину.

Если же MySQL находится в состоянии active, значит первый этап завершен.

Проводим первоначальную защиту MySQL

MySQL уже установлен и запущен. Но пока это только рабочая база в техническом смысле.

Перед тем как создавать пользователей, выдавать права и тем более открывать удаленный доступ, имеет смысл привести базовую конфигурацию в более аккуратное состояние.

Для этого в MySQL есть специальный мастер: mysql_secure_installation

Он не превращает сервер в неприступную крепость одной кнопкой, но помогает пройти несколько важных начальных шагов и убрать очевидно лишние вещи.

Зачем нужен mysql_secure_installation

После установки MySQL часть настроек остается рассчитанной на первичную настройку и локальную работу.

Мастер mysql_secure_installation помогает проверить и изменить несколько вещей, связанных с доступом к серверу базы данных.

В зависимости от установленной версии MySQL он может предложить:

  • Настроить требования к паролям;
  • Изменить способ защиты административной учетной записи;
  • Удалить анонимных пользователей;
  • Запретить нежелательный удаленный вход для root;
  • Удалить тестовую базу;
  • Применить изменения в таблицах привилегий.

Сначала убираем то, что не понадобится в нормальной эксплуатации, а уже потом создаем собственную схему доступа.

Можно представить это как подготовку новой квартиры перед заселением. Сначала меняем временные ключи и убираем лишние проходы, а уже потом решаем, кому и от какой комнаты выдавать доступ.

При этом важно понимать, что mysql_secure_installation — только первый слой.

Он не заменяет:

  • Отдельного пользователя для приложения;
  • Нормальное ограничение прав;
  • Firewall;
  • Резервные копии;
  • Безопасную настройку удаленного доступа.

Этими вещами мы займемся дальше.

Запускаем мастер защиты

Запускаем: sudo mysql_secure_installation

Дальше мастер будет задавать вопросы по очереди.

Точный набор и формулировки могут немного отличаться в зависимости от версии MySQL и того, как настроена административная учетная запись.

Если предлагается компонент проверки сложности паролей, MySQL может спросить, хотите ли вы его использовать.

Для реального сервера это полезный дополнительный барьер: он не позволяет случайно поставить совсем примитивный пароль вроде: 123456

Дальше мастер может предложить удалить анонимных пользователей.

На обычном сервере они, как правило, не нужны, поэтому такой доступ лучше убрать.

Следующий важный пункт касается административной учетной записи root.

Root в MySQL — это пользователь с максимально широкими правами внутри самой СУБД. Это не совсем то же самое, что Linux-пользователь root, хотя название одинаковое.

Поэтому использовать MySQL root как повседневную учетную запись приложения — крайне плохая идея.

Приложению позже создадим собственного пользователя с ограниченными правами.

Также мастер может предложить удалить тестовую базу и доступ к ней.

Если она не нужна, оставлять ее смысла нет.

По ходу прохождения лучше читать каждый вопрос, а не автоматически отвечать Y на всё подряд. Это всё-таки не очередное пользовательское соглашение из компьютерной игры, которое мы привыкли принимать не глядя. Хотя и там иногда стоит читать внимательнее — история с условиями Subnautica 2 хорошо показала, насколько занятные пункты могут скрываться внутри EULA. 

Игроков особенно возмутили формулировки о том, что покупка игры фактически дает не право собственности на неё, а лишь лицензию на использование.

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

На фоне того, сколько времени люди способны вкладывать в моды, арты и другой фанатский контент, неудивительно, что EULA быстро стала отдельной темой для обсуждения в игровом сообществе.

Что именно меняется после настройки

После завершения mysql_secure_installation сам MySQL работает примерно так же, как и раньше: сервис остается запущенным, базы продолжают существовать, а порт не исчезает.

Меняется прежде всего модель доступа.

До настройки условно можно представить состояние так:

То есть мы пока не сделали сервер «безопасным навсегда». Это ещё не конец.

Мы лишь расчистили стартовую площадку.

Настоящая безопасность базы дальше будет строиться из нескольких слоев:

Уровень Что будем делать 
Учетные записи Создадим отдельного пользователя 
Права Дадим ему доступ только к нужной базе 
Сеть Ограничим удаленное подключение 
Firewall Разрешим порт только нужному IP 
Backup Сделаем резервную копию и проверим восстановление 

После завершения мастера можно еще раз убедиться, что MySQL продолжает работать: systemctl is-active mysql

Ожидаем: active

Создаем базу и отдельного пользователя

Первоначальную защиту мы прошли. 

Следующий шаг — создать рабочую базу и отдельную учетную запись для приложения.

Здесь важно не идти по самому короткому пути.

Можно было бы подключить приложение к MySQL под root, выдать ему полный доступ и забыть об этом до первой проблемы.

Но это примерно как выдать кассиру ключи не только от кассы, но еще от бухгалтерии, серверной и кабинета директора. Что будет – можете сами догадаться.

Почему приложение не должно работать от root

Учетная запись root внутри MySQL обладает практически полным административным доступом к серверу базы данных.

Она может:

  • Создавать и удалять базы;
  • Создавать пользователей;
  • Менять права;
  • Удалять таблицы;
  • Изменять системные настройки;
  • Работать с объектами, которые вообще не относятся к конкретному приложению.

Для администратора это нормально.

Для обычного backend-приложения — избыточно.

Представим, что у нас есть интернет-магазин. Ему нужна одна база: shop_db

Приложению совершенно незачем получать доступ к:

  • analytics_db
  • internal_tools
  • another_project

Если такие базы появятся на том же сервере позже.

Поэтому правильнее разделить роли:

Здесь работает один из базовых принципов безопасности — principle of least privilege, или принцип минимальных привилегий.

Его идея довольно проста: пользователь получает ровно столько прав, сколько ему нужно для работы, и не больше.

Это полезно не только против взлома.

Даже обычная ошибка в приложении становится менее опасной, если его учетная запись физически не может удалить чужую базу или изменить настройки всего MySQL Server.

С теорией разобрались. Теперь создадим такую схему на практике.

Заходим в MySQL

На Ubuntu административный доступ к MySQL часто настраивается так, что локальный системный пользователь с правами sudo может войти без отдельного ввода MySQL-пароля.

Попробуем: sudo mysql

Если всё в порядке, приглашение терминала изменится примерно на: mysql>

Теперь команды выполняются уже не в обычной Linux-shell, а внутри MySQL.

Это легко отличить:

  • ubuntu@server:~$ — мы находимся в Ubuntu.
  • mysql> — уже разговариваем непосредственно с сервером базы данных.

Для начала можно посмотреть существующие базы: SHOW DATABASES;

Увидим несколько системных баз вроде:

  • information_schema
  • mysql
  • performance_schema
  • sys

Их лучше воспринимать как служебную часть MySQL. Наш проект туда складывать не нужно.

Для приложения создадим отдельную базу.

Создаем новую базу данных

Создадим: CREATE DATABASE shop_db;

Если команда выполнена успешно, MySQL ответит примерно: Query OK

Проверим: SHOW DATABASES;

Теперь среди остальных должна появиться: shop_db

Но база данных — это пока просто пустое пространство.

Считайте, что  мы построили отдельную комнату, но еще никому не выдали ключ.

Следующий шаг — создать пользователя, который сможет работать именно с ней.

Создаем отдельного пользователя

Создадим учетную запись: CREATE USER 'shop_app'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD';

Здесь каждая часть имеет значение:

Часть Что означает 
shop_app Имя пользователя MySQL 
localhost Откуда ему разрешено подключаться 
IDENTIFIED BY Задает пароль 
STRONG_PASSWORD Пароль пользователя 

Пока используем: 'localhost'

Это означает, что пользователь сможет подключаться только с самого VPS.

И это важный момент: в MySQL пользователь определяется не только именем, но и источником подключения.

То есть 'shop_app'@'localhost' и 'shop_app'@'203.0.113.25' для MySQL — фактически две разные учетные записи.

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

Позже, когда будем настраивать удаленное подключение, это нам очень пригодится.

Пароль, разумеется, используйте свой и достаточно сложный.

Выдаем только нужные права

Пользователь создан, но пока практически ничего не умеет делать с shop_db.

Теперь выдадим ему права: GRANT ALL PRIVILEGES ON shop_db.* TO 'shop_app'@'localhost';

Разберем выражение: shop_db.*

Звездочка здесь означает: все объекты внутри базы shop_db

То есть права распространяются на таблицы и другие объекты именно этой базы, а не на весь MySQL Server.

Вот визуальная схема для упрощения:

Для простого приложения ALL PRIVILEGES в пределах одной собственной базы часто удобно.

Но и это не всегда обязательно.

Если приложение, например, должно только читать данные, можно выдать значительно более узкий набор прав.

Например: GRANT SELECT ON shop_db.* TO 'shop_app'@'localhost';

Или чтение и изменение: GRANT SELECT, INSERT, UPDATE, DELETE ON shop_db.* TO 'shop_app'@'localhost';

То есть доступ можно собирать буквально как конструктор:

Право Что позволяет 
SELECT Читать данные 
INSERT Добавлять записи 
UPDATE Изменять записи 
DELETE Удалять записи 
CREATE Создавать таблицы и другие объекты 
DROP Удалять объекты 

И вот здесь принцип минимальных привилегий превращается из красивой теории в вполне конкретные SQL-команды.

Если приложение должно выполнять миграции и самостоятельно создавать таблицы, ему понадобится больше прав.

Если оно только читает аналитику — SELECT может быть достаточно.

После GRANT в современных версиях MySQL права применяются сразу. Отдельный FLUSH PRIVILEGES после обычных CREATE USER и GRANT обычно не требуется.

Проверяем доступ

Теперь мало просто поверить, что команды выполнились.

Сначала посмотрим, какие права получил пользователь: SHOW GRANTS FOR 'shop_app'@'localhost';

В выводе должна появиться строка с доступом к: shop_db.*

Теперь выйдем из административной сессии: EXIT;

И попробуем подключиться уже как приложение: mysql -u shop_app -p

MySQL попросит пароль.

После входа проверим: SHOW DATABASES;

Пользователь должен видеть доступные ему базы, включая: shop_db

Переключимся на нее: USE shop_db;

Для окончательной проверки можно создать небольшую тестовую таблицу:

CREATE TABLE test_items (

    id INT AUTO_INCREMENT PRIMARY KEY,

    name VARCHAR(100) NOT NULL

);

Добавим запись:

INSERT INTO test_items (name)

VALUES ('MySQL works');

И прочитаем ее: SELECT * FROM test_items;

Ожидаем примерно (см. скриншот главы):

Теперь у нас появилась полноценная рабочая цепочка:

При этом административная учетная запись root остается отдельно и не участвует в обычной работе приложения.

Пока, правда, наш пользователь может подключаться только с самого VPS.

Это самый простой и безопасный вариант, если приложение работает на том же сервере. Но иногда база и приложение находятся на разных машинах — и тогда придется открыть удаленный доступ.

Настраиваем безопасное удаленное подключение

Локально всё уже работает правильно: приложение на том же VPS может подключаться к shop_db через пользователя shop_app, а сам MySQL не торчит наружу без необходимости.

Для многих проектов на этом вообще можно остановиться.

Если backend и база живут на одном сервере, безопаснее так и оставить: приложение ходит в MySQL через localhost, а порт 3306 не приходится публиковать в интернет.

Но бывают ситуации, когда удаленный доступ действительно нужен.

Например:

  • Приложение работает на другом VPS;
  • К базе подключается отдельный backend-сервер;
  • Администратор работает с БД с выделенной машины;
  • Несколько сервисов используют один MySQL Server.

Тогда появляется новая задача: открыть MySQL наружу, но не для всех подряд.

Именно здесь легко сделать ошибку в стиле: 0.0.0.0:3306 + firewall открыт всем + пользователь разрешен с любого адреса

Технически удаленное подключение заработает.

Но заодно любой человек из интернета сможет хотя бы попытаться подключиться к вашему MySQL Server и гарантированно будет это делать.

Нам такой аттракцион не нужен.

Почему MySQL не стоит открывать всему интернету

MySQL обычно работает на TCP-порту: 3306

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

Сам по себе открытый порт не означает мгновенный взлом. Но он дает посторонним возможность:

  • Находить ваш MySQL при автоматическом сканировании;
  • Постоянно подбирать логины и пароли;
  • Искать уязвимости конкретной версии сервера;
  • Создавать лишнюю нагрузку;
  • Использовать ошибочно выданные права, если где-то в настройке уже допущена другая ошибка.

Получается немного странная ситуация: мы только что аккуратно создавали отдельного пользователя с ограниченными правами, а затем сами выставляем дверь MySQL прямо на улицу и говорим всем желающим: «ну попробуйте открыть».

Поэтому удаленный доступ лучше строить сразу в несколько слоев:

Здесь важно, что ограничения не дублируют друг друга впустую.

Они страхуют друг друга.

Если ошиблись в одном месте, второй слой все еще может остановить нежелательное подключение.

Сначала разберемся с тем, на каких сетевых адресах сам MySQL вообще принимает соединения.

Настраиваем bind-address

По умолчанию MySQL на Ubuntu обычно настроен так, чтобы принимать соединения только локально.

Проверить, где он слушает порт 3306, можно командой: sudo ss -lntp | grep 3306

Если видим что-то вроде 127.0.0.1:3306, значит MySQL принимает подключения только с самого VPS.

Для локального приложения это идеально.

Но удаленная машина обратиться к 127.0.0.1 нашего сервера не может. Для нее этот адрес вообще означает ее собственный localhost.

Поэтому при необходимости удаленного доступа нужно изменить сетевой адрес, который слушает MySQL.

Основной конфигурационный файл обычно находится здесь: /etc/mysql/mysql.conf.d/mysqld.cnf

Откроем его: sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Найдем строку: bind-address = 127.0.0.1

Здесь есть несколько вариантов.

Можно указать конкретный IP интерфейса VPS, если MySQL должен слушать только его.

Либо использовать: bind-address = 0.0.0.0

0.0.0.0 означает: принимать IPv4-соединения на всех сетевых интерфейсах сервера.

И тут важно не перепутать две вещи.

0.0.0.0 не означает, что любой человек автоматически получил доступ к базе.

Это лишь означает, что MySQL теперь готов принимать сетевые соединения не только через localhost.

Кто реально сможет пройти дальше, мы ограничим отдельно пользователем и firewall.

После изменения сохраняем файл и перезапускаем MySQL: sudo systemctl restart mysql

Проверяем: systemctl is-active mysql

И снова: sudo ss -lntp | grep 3306

Если использовали 0.0.0.0, можем увидеть примерно: 0.0.0.0:3306

То есть сетевую дверь мы открыли.

Теперь нужно решить, кому именно разрешено через нее входить.

Ограничиваем пользователя по IP

Ранее мы создали: 'shop_app'@'localhost'

Такой пользователь может подключаться только локально.

Если приложение теперь находится, например, на другом сервере с IP 198.51.100.25, можно создать отдельную учетную запись: CREATE USER 'shop_app'@'198.51.100.25' IDENTIFIED BY 'STRONG_PASSWORD';

И выдать ей права только на нашу базу: GRANT ALL PRIVILEGES ON shop_db.* TO 'shop_app'@'198.51.100.25';

Теперь MySQL различает две учетные записи:

  • shop_app@localhost
  • shop_app@198.51.100.25

Хотя имя пользователя одинаковое, источники подключения разные.

Это одна из очень полезных особенностей модели доступа MySQL.

Можно буквально сказать: этот пользователь существует, но входить под ним разрешено только с конкретного адреса.

А вот вариант 'shop_app'@'%' означает подключение с любого host.

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

Поэтому вместо CREATE USER 'shop_app'@'%' …, лучше использовать конкретный IP: CREATE USER 'shop_app'@'198.51.100.25' ...

Проверим права: SHOW GRANTS FOR 'shop_app'@'198.51.100.25';

Таким образом MySQL уже знает, кому разрешено подключаться.

Но запросу еще нужно добраться до самого порта 3306.

За это отвечает следующий слой.

Открываем порт 3306 только для нужного адреса

Если на сервере используется UFW, не нужно делать так: sudo ufw allow 3306

Такая команда разрешает подключения к MySQL отовсюду.

Нам нужно гораздо точнее.

Допустим, удаленный сервер имеет IP: 198.51.100.25

Разрешим доступ к 3306 только ему: sudo ufw allow from 198.51.100.25 to any port 3306 proto tcp

Проверим правила: sudo ufw status

По смыслу должно получиться:

Вот теперь ограничения работают сразу на двух уровнях:

  • Firewall → пропускает только нужный IP
  • MySQL → принимает shop_app только с этого IP

Если VPS находится у облачного провайдера и там дополнительно используются Security Groups или внешний firewall, правило нужно проверить и на этом уровне.

Это важно: можно идеально настроить UFW, но забыть, что облачный firewall вообще не пропускает 3306.

Или наоборот — открыть порт в Security Group для всего интернета и случайно свести часть нашей аккуратной настройки на нет.

Проверяем удаленное подключение

Теперь переходим на разрешенную удаленную машину.

Подключение выглядит так: mysql -h 203.0.113.10 -u shop_app -p

Где 203.0.113.10 — публичный IP MySQL-сервера.

После ввода пароля должно появиться приглашение: mysql>

Проверим базу: USE shop_db;

И попробуем прочитать тестовую таблицу, которую создавали раньше: SELECT * FROM test_items;

Если получаем нашу запись:

Значит цепочка действительно работает:

Для полноты можно провести и обратную проверку: попробовать подключиться с IP, которому доступ не разрешали.

Такое соединение должно быть заблокировано еще до нормальной авторизации в MySQL.

И тут важно сделать небольшую оговорку.

Даже схема с ограничением по IP намного лучше полностью открытого 3306, но для чувствительных production-систем удаленное подключение к MySQL часто дополнительно защищают частной сетью, VPN или SSH-туннелем, или настраивают SSL подключение с проверкой сертификата.

Причина простая: IP-фильтрация контролирует, кто может добраться до порта, но сама по себе не решает все вопросы защиты трафика между разными сетями, по умолчанию подключение не шифруется и происходит передача данных открытым текстом.

Для нашей базовой конфигурации главное уже сделано: MySQL не открыт всему интернету, пользователь ограничен конкретным источником, а firewall пропускает только нужный адрес.

Если же удаленное подключение после всего этого не заработало, не стоит сразу откатывать настройки и открывать порт всем подряд.

В следующей главе разберем основные причины: от bind-address и firewall до классического Access denied.

Если удаленное подключение не работает

Здесь будем проверять цепочку по уровням:

То есть двигаемся от сети к самой базе и на каждом шаге проверяем только одну вещь.

MySQL слушает только localhost

Начнем с самого частого случая.

На клиентской машине мы выполняем mysql -h 203.0.113.10 -u shop_app -p, а соединение не устанавливается.

Проверим на MySQL-сервере, где вообще слушается порт 3306: sudo ss -lntp | grep 3306

Если видим 127.0.0.1:3306, значит MySQL принимает только локальные подключения.

Удаленная машина до такого адреса добраться не сможет.

Вспомним почему.

127.0.0.1 всегда означает: этот же самый компьютер.

Поэтому: 127.0.0.1 на VPS ≠ 127.0.0.1 на вашем компьютере

Каждая машина воспринимает localhost как саму себя.

Откроем конфигурацию: sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

И проверим: bind-address = 127.0.0.1

Если удаленный доступ действительно нужен, меняем значение на подходящий адрес. В нашем базовом примере: bind-address = 0.0.0.0

После изменения: sudo systemctl restart mysql

Проверяем: systemctl is-active mysql

И снова: sudo ss -lntp | grep 3306

Теперь ожидаем, например: 0.0.0.0:3306

Если MySQL начал слушать внешний интерфейс, переходим к следующему звену.

Пользователь не разрешен для удаленного IP

Допустим, сеть настроена правильно, но MySQL всё равно отказывает во входе.

Здесь стоит вспомнить особенность, которую мы разбирали ранее 'shop_app'@'localhost' и 'shop_app'@'198.51.100.25' для MySQL — разные учетные записи.

Поэтому наличие пользователя с именем shop_app еще не означает, что ему разрешено подключаться откуда угодно.

Зайдем в MySQL: sudo mysql

И посмотрим учетные записи: SELECT User, Host FROM mysql.user WHERE User = 'shop_app';

Например, если видим только:

всё становится понятно.

Пользователь существует, но только для локальных подключений.

Для клиента с IP 198.51.100.25 нужна соответствующая учетная запись: CREATE USER 'shop_app'@'198.51.100.25' IDENTIFIED BY 'STRONG_PASSWORD';

И права: GRANT ALL PRIVILEGES ON shop_db.* TO 'shop_app'@'198.51.100.25';

Проверяем: SHOW GRANTS FOR 'shop_app'@'198.51.100.25';

Здесь часто совершают соблазнительную ошибку и вместо конкретного IP создают: 'shop_app'@'%'

Это действительно может моментально «починить» подключение.

Но % означает любой host.

То есть мы устраняем проблему примерно как человек, который не смог открыть дверь своим ключом и поэтому решил снять её с петель.

Подключение заработает, но способ решения выбран сомнительный.

Если известен постоянный IP клиента, лучше ограничиться им.

Firewall блокирует порт 3306

Следующий уровень — сеть.

MySQL может прекрасно слушать 0.0.0.0:3306 и пользователь может быть создан правильно, но запрос просто не доходит до сервера.

Проверим UFW: sudo ufw status

Для нашего разрешенного клиента должно существовать правило примерно такого смысла: 198.51.100.25 → 3306/tcp → ALLOW

Если его нет: sudo ufw allow from 198.51.100.25 to any port 3306 proto tcp

После этого снова: sudo ufw status

Но UFW — не единственный возможный firewall.

Если VPS работает у облачного провайдера, там может быть дополнительная Security Group или сетевой firewall.

И тогда можно получить интересную картину:

Потому что пакет блокируется еще до того, как доходит до самой Ubuntu.

Поэтому при сетевых проблемах проверяйте оба уровня:

С разрешенной удаленной машины можно также проверить, доступен ли сам TCP-порт: nc -vz 203.0.113.10 3306

Если nc установлен, успешное соединение покажет, что до порта хотя бы удается достучаться.

Если порт вообще недоступен, искать ошибку в SQL-правах пока рано.

Получаем Access denied

А вот если клиент до MySQL уже доходит, но получает сообщение вроде: ERROR 1045 (28000): Access denied for user 'shop_app'@'198.51.100.25' — это уже хороший диагностический сигнал.

Сеть работает.

MySQL получил запрос.

Теперь проблема находится в авторизации или правах пользователя.

Обратите внимание на сам текст ошибки: 'shop_app'@'198.51.100.25'

MySQL прямо показывает, какого пользователя и с какого адреса он сейчас пытается проверить.

Это очень полезно.

Если вы ожидали: shop_app@198.51.100.25, а в ошибке появился другой IP, значит сервер видит клиента не так, как вы предполагали. Такое бывает, например, из-за NAT или промежуточной сетевой инфраструктуры.

Проверим существующих пользователей: SELECT User, Host FROM mysql.user WHERE User = 'shop_app';

Затем права: SHOW GRANTS FOR 'shop_app'@'198.51.100.25';

И, конечно, убедимся, что вводим пароль именно от этой учетной записи.

Здесь полезно разделять два типа ошибок:

Эта разница сильно сокращает область поиска.

Проверяем логи MySQL

Если предыдущие проверки ничего очевидного не показали, пора перестать гадать и посмотреть, что пишет сам MySQL.

Сначала можно проверить сервис через systemd: sudo journalctl -u mysql --no-pager -n 100

А для просмотра сообщений в реальном времени: sudo journalctl -u mysql -f

После этого повторяем удаленное подключение и смотрим, появляются ли новые записи.

В зависимости от конфигурации MySQL отдельный error log также может находиться в: /var/log/mysql/error.log

Посмотреть последние строки: sudo tail -n 50 /var/log/mysql/error.log

Логи особенно полезны, если проблема связана не просто с неправильным паролем, а с запуском сервиса, сетевой конфигурацией, TLS, плагином аутентификации или другими внутренними ошибками MySQL.

В итоге всю диагностику можно свести к одной небольшой карте:

Главное здесь — не пытаться чинить сразу все уровни.

Если порт недоступен, права пользователя пока вообще ни при чем. Если MySQL уже отвечает Access denied, значит сеть, наоборот, свою часть работы выполнила.

Когда удаленное подключение заработало, остается решить следующую задачу. Ведь ограничить доступ к базе — это только половина безопасности. Нужно еще иметь возможность вернуть данные, если их случайно удалили или повредили.

Поэтому дальше сделаем резервную копию через mysqldump.

Создаем резервную копию через mysqldump

Делаем backup одной базы

Для нашей тестовой базы shop_db команда выглядит так: mysqldump --no-tablespaces -u shop_app -p shop_db > shop_db_backup.sql

После запуска MySQL попросит пароль пользователя.

Параметр --no-tablespaces отключает выгрузку информации о tablespaces. Это позволяет создать дамп от имени пользователя с ограниченными правами без выдачи ему глобальной привилегии PROCESS.

Без этого параметра в MySQL 8 можно получить ошибку: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation

В нашем случае выдавать shop_app глобальную привилегию PROCESS только ради резервного копирования не требуется, поэтому используем --no-tablespaces. 

Разберем команду по частям:

Часть Что делает 
mysqldump Запускает создание дампа 
-u shop_app Подключается от имени пользователя shop_app
-p Запрашивает пароль 
shop_db Указывает, какую базу сохраняем 
Перенаправляет вывод в файл 
shop_db_backup.sql Имя файла резервной копии 
--no-tablespaces Не включает в дамп информацию о tablespaces и позволяет обойтись без глобальной привилегии PROCESS

Важно: пользователь, от которого выполняется mysqldump, должен иметь достаточно прав для чтения нужных объектов базы.

Если shop_app используется только приложением и его права специально урезаны, для резервного копирования иногда удобнее создать отдельную учетную запись с правами только на backup.

Для нашего примера оставим всё проще и используем текущего пользователя.

После выполнения команды файл появится в текущем каталоге.

Например: ls -lh shop_db_backup.sql

Именно этот файл теперь содержит SQL-инструкции, которые описывают содержимое базы на момент создания копии.

Проверяем созданный файл

Сам факт существования файла еще не гарантирует, что backup получился нормальным.

Иногда можно получить пустой файл, дамп с ошибкой или вообще сохранить не то, что планировали.

Поэтому сначала посмотрим размер: ls -lh shop_db_backup.sql

Для базы с данными размер должен быть больше нуля.

Можно также заглянуть в начало файла: head -n 20 shop_db_backup.sql

Там будут служебные SQL-команды и комментарии mysqldump.

А чтобы убедиться, что в дампе действительно присутствует наша тестовая таблица: grep -n "test_items" shop_db_backup.sql

Если имя таблицы находится, значит она попала в резервную копию.

Но тут есть важный нюанс.

Проверить, что файл существует, — это еще не то же самое, что проверить backup полностью.

Настоящая проверка резервной копии — это восстановить ее и убедиться, что данные действительно возвращаются.

Именно этим мы займемся в следующей главе.

Пока же у нас есть сам дамп.

Не оставляем пароль в истории команд

Есть соблазн написать так: mysqldump --no-tablespaces -u shop_app -pMY_SECRET_PASSWORD shop_db > shop_db_backup.sql

И команда короче, и пароль вводить не надо.

Но именно поэтому так делать обычно и не стоит.

Если пароль указать прямо в командной строке, он может попасть:

  • В историю shell;
  • В список процессов на время выполнения команды;
  • В логи или скрипты;

Думаю, что несложно догадаться, чем это черевато.

Лучше оставить просто: mysqldump --no-tablespaces -u shop_app -pMY_SECRET_PASSWORD shop_db > shop_db_backup.sql

А после ввести пароль уже после запроса: Enter password:

При вводе символы обычно не отображаются — это нормально. Не переживайте.

Если backup выполняется автоматически по расписанию, вводить пароль вручную каждый раз уже неудобно. В таком случае лучше использовать отдельный защищенный конфигурационный файл или другой механизм хранения credentials, а не вписывать пароль прямо в cron-команду.

И еще один важный момент: сам файл: shop_db_backup.sql тоже может содержать чувствительные данные.

Если в базе есть пользователи, email, заказы, токены или другая внутренняя информация, SQL-дамп фактически становится копией этих данных.

Поэтому backup не стоит хранить в случайном публичном каталоге или раздавать права на чтение всем пользователям сервера.

Например, можно ограничить доступ к файлу: chmod 600 shop_db_backup.sql

Теперь читать и изменять его сможет только владелец.

Получается, резервная копия — это не просто «страховочный файл».

Ее тоже нужно защищать примерно так же внимательно, как и саму базу.

Восстанавливаем базу из резервной копии

Backup у нас уже есть. Но сам по себе файл shop_db_backup.sql еще не доказывает, что в критический момент он действительно спасет данные.

Поэтому теперь сделаем то, что многие откладывают «на потом»: попробуем восстановить дамп на практике.

И лучше делать это не поверх рабочей базы, а в отдельную тестовую.

Почему? Потому что восстановление — это уже не чтение, а запись данных. Если случайно импортировать дамп не туда, можно получить дубликаты, конфликты или вообще перезаписать то, что трогать не планировалось.

Подготавливаем базу для восстановления

Создадим отдельную базу: CREATE DATABASE shop_db_restore;

Для этого зайдем в MySQL под административной учетной записью: sudo mysql

Далее выполним: CREATE DATABASE shop_db_restore;

Проверим: SHOW DATABASES;

В списке должна появиться: shop_db_restore

Теперь дадим нашему пользователю права на тестовую базу: GRANT ALL PRIVILEGES ON shop_db_restore.* TO 'shop_app'@'localhost';

После обычного GRANT права применяются сразу.

Выйдем: EXIT;

Теперь у нас есть безопасная площадка для проверки:

Иными словами, рабочую базу мы вообще не трогаем.

Импортируем SQL-дамп

Теперь восстановим содержимое дампа в новую базу.

Команда выглядит так: mysql -u shop_app -p shop_db_restore < shop_db_backup.sql

Здесь уже используется обратное перенаправление:

  • Ранее мы делали: mysqldump → файл
  • Теперь наоборот: файл → mysql

Разберем команду:

Часть Что делает 
mysql Запускает клиент MySQL 
-u shop_app Подключается как shop_app
-p Запрашивает пароль 
shop_db_restore Указывает базу, куда импортируем данные 
< shop_db_backup.sql Передает SQL-команды из дампа в MySQL 

После ввода пароля команда может завершиться вообще без красивого сообщения вроде: Restore completed successfully

И это нормально.

Если ошибок не появилось, MySQL просто выполнил SQL-инструкции из файла.

По сути происходит следующее:

То есть mysql читает дамп сверху вниз и воспроизводит команды, которые были сохранены mysqldump.

Но верить отсутствию ошибок на слово мы не будем. Проверим результат сами.

Проверяем восстановленные данные

Подключимся к восстановленной базе: mysql -u shop_app -p shop_db_restore

Посмотрим таблицы: SHOW TABLES;

Если наш backup действительно содержал тестовую таблицу, увидим: test_items

Теперь посмотрим данные: SELECT * FROM test_items;

Ожидаем ту же запись, которую создавали раньше:

Вот теперь резервную копию уже можно считать действительно проверенной.

Мы прошли полный цикл:

Именно поэтому проверка восстановления не менее важна, чем само создание backup.

Файл может существовать, иметь нормальный размер и даже содержать знакомые SQL-команды. Но только успешное восстановление показывает, что из него действительно можно вернуть рабочие данные.

Для реальной production-базы проверка обычно идет глубже: смотрят количество таблиц, ключевые записи, связи между данными и работу самого приложения уже на восстановленной копии.

Но для нашего тестового стенда достаточно убедиться, что структура и данные вернулись.

Теперь у нас есть не только работающий MySQL Server с ограниченным доступом, но и проверенный сценарий восстановления.

Отдельно стоит отметить, что резервная копия, которая хранится локально - защищает от сбоев самой базы, но не VPS целиком, поэтому на "боевой" среде бэкап необходимо забирать и складировать в других местах. Также файл .sql является по сути текстом, и неплохо жмётся в архив - но это мы оставим за рамками статьи.

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

Финальная проверка

Начнем с самого сервиса:

Что проверяем Команда Что ожидаем 
MySQL запущен systemctl is-active mysql active 
MySQL включен в автозагрузку systemctl is-enabled mysql enabled 
Порт 3306 слушается sudo ss -lntp | grep 3306 Нужный адрес и порт 3306
Пользователь существует SELECT User, Host FROM mysql.user WHERE User = 'shop_app'; Нужный user@host

Теперь проверим доступ и резервное копирование: 

Что проверяем Способ Что ожидаем 
Локальное подключение mysql -u shop_app -p Успешный вход 
Удаленное подключение mysql -h 203.0.113.10 -u shop_app -p Успешный вход с разрешенного IP 
Права пользователя SHOW GRANTS FOR 'shop_app'@'HOST'; Доступ только к нужной базе 
Backup создан ls -lh shop_db_backup.sql Файл существует и не пустой 
Restore работает SELECT * FROM test_items; в shop_db_restoreВосстановленные данные на месте 

Если все эти проверки проходят, значит мы действительно собрали рабочую конфигурацию целиком:

Теперь уже можно говорить, что MySQL не просто установлен, а подготовлен к нормальной эксплуатации.

Заключение

На этом настройка завершена.

Мы установили MySQL Server на Ubuntu, прошли первоначальную защиту, создали отдельную базу и пользователя, ограничили его права и настроили удаленный доступ так, чтобы порт 3306 не был открыт всему интернету.

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

FAQ

Можно ли использовать одного MySQL-пользователя сразу для нескольких приложений?

Технически да, но обычно лучше разделять учетные записи.

Например:

  • shop_app → shop_db
  • analytics_app → analytics_db
  • admin_panel → admin_db

Так проще контролировать права и понимать, какой сервис что может делать и что делает по факту.

Если все приложения используют одного пользователя с широкими правами, ошибка или компрометация одного проекта автоматически дает доступ и к остальным данным.

MySQL как раз позволяет различать учетные записи не только по имени, но и по источнику подключения в формате 'user'@'host'.

Нужно ли открывать порт 3306, если приложение работает на том же VPS?

Нет.

Если приложение и MySQL находятся на одном сервере, можно оставить базу доступной только через localhost: 127.0.0.1:3306

Тогда приложение будет обращаться к MySQL внутри VPS, а внешний порт вообще не понадобится.

Удаленный доступ стоит включать только тогда, когда для него действительно есть задача.

Что безопаснее: 'shop_app'@'%' или пользователь с конкретным IP?

Конкретный IP дает более жесткое ограничение.

Запись 'shop_app'@'%', разрешает этой учетной записи сопоставляться с подключениями с любого host, тогда как 'shop_app'@'198.51.100.25' ограничивает источник конкретным адресом. 

Если адрес клиента известен и постоянен, лучше использовать его и дополнительно продублировать ограничение в firewall.

Достаточно ли закрыть MySQL через UFW?

UFW — важный слой, но лучше не делать его единственной защитой.

Нормальная схема выглядит так:

Ubuntu позволяет создавать правило firewall для конкретного IP и конкретного TCP-порта, поэтому 3306 необязательно открывать всему интернету.

Почему mysqldump называется логическим backup?

Потому что он не копирует физические файлы MySQL один в один.

mysqldump формирует набор SQL-инструкций, с помощью которых можно воспроизвести определения объектов базы и ее данные.

Упрощенно:

Именно поэтому такой дамп удобно читать, переносить и импортировать обратно через клиент mysql.

Почему существование файла backup.sql еще не означает, что backup рабочий?

Потому что файл может оказаться пустым, неполным или содержать дамп не той базы.

Даже нормальный на вид SQL-файл лучше хотя бы периодически восстанавливать в отдельную тестовую базу.

В нашем случае проверка выглядела так:

Только после этого мы действительно знаем, что из копии можно вернуть данные.

Нужно ли запускать FLUSH PRIVILEGES после каждого GRANT?

Нет, если права меняются обычными командами CREATE USER, GRANT, ALTER USER и другими штатными SQL-инструкциями.

MySQL сам обновляет информацию о привилегиях.

FLUSH PRIVILEGES становится нужен в других сценариях, например после прямого ручного изменения grant tables. Поэтому добавлять его после каждого GRANT «на всякий случай» не требуется.

Что именно делает mysql_secure_installation?

Это не автоматическая кнопка «сделать MySQL безопасным», а мастер первоначальной настройки.

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

После него всё равно остаются отдельные задачи: создать пользователей, правильно выдать права, настроить сеть, firewall и backup.

Список источников

  1. MySQL 8.4 Reference Manual — mysql_secure_installation
  2. MySQL 8.4 Reference Manual — Specifying Account Names / CREATE USER
  3. MySQL 8.4 Reference Manual — mysqldump
  4. Ubuntu Server Documentation — Firewall

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами