в Администририрование

Так уж сложилось, что время от времени приходится поднимать сайты на хостинге руцентра. Как правило это небольшие сайты-визитки или проекты, для которых реализация на дедике характеризуется как «жирно будет».

Итак, отбросим причины в сторону. Первым делом — что нам понадобится для того чтоб всё сделать «под ключ», т.е. владельцем домена/хостинга был заказчик (как физ.лицо), а ты был лишь лицом, которое выполняет работу? От заказчика тебе потребуется:

  • Фотографии/сканы разворота паспорта и страницы с пропиской;
  • Логин/пароль от почтового ящика, который будет фигурировать при регистрации;
  • Необходимая сумма денег для оплаты требуемых услуг.

Вопрос: При регистрации домена может сразу встать вопрос — делегировать домен на руцентр или, например, сразу же на DNS-хостинге от Яндекса?

Ответ: На руцентр. Так как для делегирования на Яндекс потребуется подтвердить права на владение доменом. И проще всего это сделать с помощью проверки наличия определенного файла с кодом в корне сайта. Поэтому — смело всё делай по дефолту, потом переделегируем, если потребность такая возникнет.

Сразу скажу — если «с сайта» будут отправляться письма и при этом хостить DNS у Яндекса — возникнут проблемы с отправкой писем. DKIM настроить на Яндекс будет невозможно, а у руцентра он принципиально не настраиваемый. Поэтому считай это тонкостью использования руцентра в целом.

Итак, считаем что аккаунт у нас заведен, домен — зарегистрирован, хостинг — заказан. Не забудь а «Личных данных» приложить сканы/фотографии паспорта клиента для верификации личности, так правильнее будет.

Настройка почты

Переходим «Панель управления» → «Почтовый сервер». Выбираем наш почтовый домен (должен быть создан автоматически; в противном случае создаем его с именем основного домена). Первым делом лезем в «Почтовые ящики» → «[email protected]_domain.ltd», и добавляем синонимы к ящику вида info@ admin@ webmaster@ abuse@. Таким образом все «важные» письма будут первым делом попадать именно на этот ящик, а владелец сайта сможет написать на своих визитках «клёвый» почтовый адрес [email protected]_domain.ltd :) Так же заходим в интерфейс самого почтового ящика (нажимаем на его адрес вверху страницы) и настраиваем переадресацию всех входящих писем на email-адрес заказчика.

Далее «Панель управления» → «Почтовый сервер» → «Параметры», и в поле «Обработка нераспознанной почты» вводим [email protected]_domain.ltd. Таким образом письма, отправленные на несуществующий ящик (например blabla@your_domain.ltd) будут уходить на [email protected]_domain.ltd, а оттуда — нашему заказчику. Если же будут заведены дополнительные адреса (например — для сотрудников нашего заказчика) — ничего перенастраивать не придется.

Настройка СУБД

Настройку СУБД производить в соответствии с используемой системой управления контента. Рекомендую «выключать» все привилегии для пользователя, которые ему критически не важны. В идеале ему должно хватать лишь INSERT UPDATE SELECT и DELETE. Но это лишь в идеале. Ещё раз — тут смотри сам.

Больше особо интересных моментов в настройке для хостинга нет.

Настройка Веб-сервера

Вот тут самое пожалуй интересное. Первым делом мы осуществляем «преднастройку» в веб-интерфейсе панели управления. Если используешь CMS «WordPress», то для тебя подойдут почти все настройки что идут «по умолчанию», но не все. Для других CMS — надо смотреть более детально и отдельно.

Выключаем WP_CRON для WordPress

В WordPress Есть такая штука как WP_CRON, которая позволяет выполнять «отложенные» действия, такие как отложенная публикация постов, проверка наличия обновлений и прочие весьма полезные штуки. Но это довольно тяжелая задача, надо признать, и выполнять её при каждом посещении пользователя/администратора сайта — иметь лишнюю задержку. Что мы делаем чтоб ситуацию исправить? Мы выключаем WP_CRON в WordPress путем добавления строки:

define('DISABLE_WP_CRON', true);

В wp-config.php, и добавляем отдельную задачу в «Панель управления» → «Веб-сервер» → «Планировщик заданий» с именем, например — «wp cron» и выполняемой командой:

/usr/local/bin/wget -O - -q "http://your_domain.ltd/wp-cron.php"

Теперь пользователи не будут ощущать задержку, а все задачи крона WP будут выполняться «в фоне» с заданным интервалом (выставь «Каждые 5 минут»).

Настраиваем модули

Переходим в «Управление модулями». Здесь нам необходимо выполнить предварительную настройку как веб-сервера (Apache, а точнее просто указать какие модули ему загружать), выбрать используемую версию php (не знаю почему, но я всё ещё пользуюсь версией 5.3), и указать необходимые параметры для php, такие как кодировка (выставляй везде UTF-8), максимальный размер загружаемого файла, и модули php (выставляй их в соответствии с требованиями сайта). Вырубай всё откровенно лишнее и неиспользуемое. После этого перезапусти веб-сервер путем нажатия на соответствующую ссылку, и ставь свою CMS. На этом шаге у тебя должно всё работать как надо. Проверь всё — отправку писем, работу всех частей как пользовательского интерфейса, так и административной части. Всё должно работать. Только после этого мы переходим к следующей части.

Перевод сервера в ручной режим

Это необходимо для того, чтоб выполнить более тонкую настройку, недоступную из веб-интерфейса. Для перевода работы веб-сервера в ручной режим переходи в «Панель управления» → «Веб-сервер» и в графе «Режим настройки» жми на «Ручной». После этого действия будут созданы конфиги на основе тех настроек, которые мы выполнили ранее. Теперь цепляемся к хостингу по ssh (реквизиты для соединения указаны в «Панель управления» → «Помощь»), и все дальнейшие настройки будем выполнять только в нем.

Настройка nginx

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

  • Отдачу статического контента с помощью nginx
  • Включить его сжатие с помощью gzip
  • Настроить его хранение на стороне клиента (вместо того, чтоб каждый раз скачивать его с нашего сервера)

Для этого выполняем в консоли:

$ cat /etc/nginx/your_domain.ltd.site.conf

И смотрим как у нас был настроен nginx в автоматическом режиме. Он отдавал статику, но сжатие и хранение статики на стороне клиента — отсутствует. Испраляем-с :) Копируем из этого конфига все location-ы (в моем случае это были location /, location ~* ^.+\.(jpg|jpeg|gif|... и location @fallback) в конфиг ~/etc/nginx/nginx.conf, вставляя их в секцию server {...}. Сделать по образу и подобию не думаю что составит трудность.

Для включения gzip-сжатия статики необходимо в секцию http {...} добавить:

  gzip_static on;
  gzip on;
  gzip_buffers 16 8k;
  gzip_comp_level 2;
  gzip_min_length 1024;
  gzip_types text/css text/plain text/json text/x-js text/javascript text/xml application/json application/x-javascript application/xml application/xml+rss application/javascript;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_http_version 1.0;

Добавить можно в принципе в любом месте перед началом секции server {...}. Теперь статика будет отдаваться значительно быстрее :)

Для того чтоб она лишний раз не загружалась с сервера, а хранилась на стороне клиента мы немного подправил location который отвечает за отдачу статики, а точнее добавим в него строку expires 30d;, чтоб получилось примерно следующее:

  location ~* ^.+\.(jpg|jpeg|gif|swf|png|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|dat|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z)$ {
    expires 30d;
    root   /home/ugra-hotel/ugra-hotel.ru/docs;
    access_log  /var/log/ugra-hotel.ru.access_log  combined;
    error_page 404 = @fallback;
    log_not_found off;
    accel_htaccess_switch on;
  }

Так же приведу для сравнения полный листинг получившегося у меня конфига:

worker_processes  2;
error_log  /dev/null;
pid        /var/run/nginx.pid;
events {
    worker_connections  2048;
}
http {
  set_real_ip_from 10.1.0.0/16;      # MSK
  set_real_ip_from 10.3.0.0/16;      # MSK
  set_real_ip_from 195.208.0.0/23;   # MSK
  set_real_ip_from 212.192.194.0/24; # NSK
  set_real_ip_from 10.12.0.0/16;     # NSK
  set_real_ip_from 212.192.193.0/24; # NSK
  set_real_ip_from 10.15.0.0/16;     # AMS
  set_real_ip_from 178.210.94.0/24;  # AMS
  real_ip_header X-Real-IP;

  gzip_static on;
  gzip on;
  gzip_buffers 16 8k;
  gzip_comp_level 2;
  gzip_min_length 1024;
  gzip_types text/css text/plain text/json text/x-js text/javascript text/xml application/json application/x-javascript application/xml application/xml+rss application/javascript;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_http_version 1.0;

  include       /usr/local/etc/nginx/mime.types;
  default_type  application/octet-stream;
  server_names_hash_bucket_size 128;
  access_log      off;
  sendfile        on;
  keepalive_timeout  65;

  #include /etc/nginx/virts_list;
  server {
    listen       10.3.138.12:80;
    server_name  your_domain.ltd your_domain.nichost.ru www.your_domain.ltd your_domain.nichost.ru;
    location / {
      proxy_pass         http://10.3.138.12:8080;
      proxy_redirect     http://your_domain.ltd:8080/ /;
      proxy_redirect     http://your_domain.nichost.ru:8080/ /;
      proxy_redirect     http://www.your_domain.ltd:8080/ /;
      proxy_redirect     http://www.your_domain.nichost.ru:8080/ /;
      proxy_set_header   Host             $host;
      proxy_set_header   X-Real-IP        $remote_addr;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      client_max_body_size       192m;
      client_body_buffer_size    128k;
      proxy_connect_timeout      90;
      proxy_send_timeout         900;
      proxy_read_timeout         900;
      proxy_buffer_size          64k;
      proxy_buffers              8 32k;
      proxy_busy_buffers_size    64k;
      proxy_temp_file_write_size 64k;
    }
    include /home/your_domain/etc/nginx/secure_wordpress.inc;

    # Set error pages
    set $errordocs /home/your_domain/your_domain.ltd/errordocs;
    error_page 400 /400.html; location = /400.html {root $errordocs;}
    error_page 401 /401.html; location = /401.html {root $errordocs;}
    error_page 403 /403.html; location = /403.html {root $errordocs;}
    error_page 404 /404.html; location = /404.html {root $errordocs;}
    error_page 408 /408.html; location = /408.html {root $errordocs;}
    error_page 500 /500.html; location = /500.html {root $errordocs;}
    error_page 501 /501.html; location = /501.html {root $errordocs;}
    error_page 502 /502.html; location = /502.html {root $errordocs;}
    error_page 503 /503.html; location = /503.html {root $errordocs;}
    error_page 504 /504.html; location = /504.html {root $errordocs;}

    # Static files location
    location ~*
  ^.+\.(jpg|jpeg|gif|swf|png|ico|mp3|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|dat|avi|ppt|txt|tar|mid|midi|wav|bmp|rtf|wmv|mpeg|mpg|mp4|m4a|spx|ogx|ogv|oga|webm|weba|ogg|tbz|js|7z)$ {
      expires 30d;
      root   /home/your_domain/your_domain.ltd/docs;
      access_log  /var/log/your_domain.ltd.access_log  combined;
      # original nic.ru line below:
      #error_page 404 = @fallback;
      log_not_found off;
      accel_htaccess_switch on;
    }
    location @fallback {
      proxy_pass http://10.3.138.12:8080;
      proxy_set_header   Host             $host;
      proxy_set_header   X-Real-IP        $remote_addr;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      client_max_body_size       192m;
      client_body_buffer_size    128k;
      proxy_connect_timeout      90;
      proxy_send_timeout         900;
      proxy_read_timeout         900;
      proxy_buffer_size          64k;
      proxy_buffers              8 32k;
      proxy_busy_buffers_size    64k;
      proxy_temp_file_write_size 64k;
    }
  }
}
if ($http_user_agent ~* ((perl|php|python|wpscan)|^(|-|_)$)) {return 403;}
if ($http_user_agent ~* (nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan|nic.ru|monitoring|semalt|virusdie|indy)) {return 444;}
if ($http_referer ~* (semalt.com|virusdie|mskshops.ru|apishops.ru)) {return 444;}
location ~* /(magmi.(php|ini)|uploadTester.asp|.*server.ca.pem|(x|humans).txt|(flashgallery|thumb_editor|html|phpinfo|xxx|bad).php|filezilla.xml)$ {return 444;}
if ($query_string ~* "^(.*)(/(.*my.cnf|self/environ|etc/passwd)|cmd=|curl+|bad.php)(.*)$") {return 444;}
location ~* .*/(fckeditor|kcfinder|ckfinder)/.*\.(php|asp(|x))$ {return 444;}
location ~ /\.ht {return 444;}

add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block;";

location ~* /((wp-config|plugin_upload|xmlrpc|wp-xmlrpc).php|(readme|license|changelog).(html|txt|md)|(debug|access|error)(.|_)log)$ {access_log off; log_not_found off;
return 404;}
location ~* /((dl-skin|nyemnyem|searchreplacedb2|wp-content|bogel|myluph|parser|routing).php|local.xml)$ {access_log off; log_not_found off; return 404;}
location ~* /.*((|.|%23)(wp-config|xmlrpc).*(php(_bak|~|#|%23)|txt|old|bak|save|orig(|inal)|swp|swo)).*$ {access_log off; log_not_found off; return 404;}
if ($query_string ~* "^(.*)(wp-config.php|dl-skin.php|xmlrpc.php|uploadify.php|admin-ajax.php|local.xml)(.*)$") {return 404;}
if ($query_string ~* "(concat.*\(|union.*select.*\(|union.*all.*select)") {return 404;}

if ($query_string ~* "author=[0-9]") {return 301 $scheme://$host/;}

location ~* /(?:uploads|files)/.*\.(php|cgi|py|pl)$ {return 404;}
location ~* /(wp|page)/.*wp-.*/.*$ {access_log off; log_not_found off; return 404;}

location = /wp-content/ {return 404;}            location = /wp-content {return 404;}
location = /wp-includes/ {return 404;}           location = /wp-includes {return 404;}
location = /wp-content/plugins/ {return 404;}    location = /wp-content/plugins {return 404;}
location = /wp-content/mu-plugins/ {return 404;} location = /wp-content/mu-plugins {return 404;}
location = /wp-content/uploads/ {return 404;}    location = /wp-content/uploads {return 404;}
location = /wp-content/themes/ {return 404;}     location = /wp-content/themes {return 404;}
location = /wp-content/languages/ {return 404;}         location = /wp-content/languages {return 404;}
location = /wp-content/languages/plugins/ {return 404;} location = /wp-content/languages/plugins {return 404;}
location = /wp-content/languages/themes/ {return 404;}  location = /wp-content/languages/themes {return 404;}
location ~ /wp-content/languages/(.+)\.(po|mo)$ {return 404;}

Теперь перезапускаем nginx и проверяем чтоб всё работало как надо с помощью, например, PageSpeed Insights:

$ /etc/rc.d/nginx restart
Настройка apache

Конфиг Apache теперь храниться по пути ~/etc/apache_2.4/httpd.conf, и для его перезапуска необходимо будет выполнить команду:

$ /etc/rc.d/httpd restart

Всё что нам необходимо сделать здесь — это добавить 2 строки перед секцией <VirtualHost *:8080>: ServerTokens Prod и ServerSignature Off, которые запрещают вывод сигнатур сервера. Если тебе потребуется ещё что-то настраивать у Apache — то делай это в этом файле.

Настройка php

Для того, чтоб веб-сервер Apache начал читать именно наш конфиг, а не тот что лежит в директории ~/etc/ — нам необходимо скопировать соответствующий в домашнюю директорию. Поясню — например, мы используем php версии 5.3. Смотрим в ~/etc/:

$ ls -l ~/etc/php*
lrwxr-xr-x  1 root  wheel    22 Jan 18 16:55 /home/your_domain/etc/php -> ../../../usr/opt/php53
lrwxr-xr-x  1 root  wheel     9 Jan 18 16:55 /home/your_domain/etc/php.ini -> php53.ini
-rw-r--r--  1 root  wheel  1106 Jan 18 16:59 /home/your_domain/etc/php53.ini
-rw-r--r--  1 root  wheel  1156 Jan 18 08:26 /home/your_domain/etc/php56.ini

Конфиг есть, но писать в него мы соответственно не можем. Копируем его в домашнюю директорию под именем php.ini:

$ cp ~/etc/php53.ini ~/php.ini

И добавляем в него строку expose_php=Off, которая скроет версию используемого php и заголовках ответов веб-сервера. Перезапускаем Apache:

$ /etc/rc.d/httpd restart

И проверяем чтоб всё работало, но ничего лишнего наружу «не светилось».

Настраиваем резервное копирование

Хоть администраторы хостинга и выполняют резервное копирование — но лишней копия всё же не будет. Хранить мы будем бэкапы 31 день (все настройки указываются в начале скрипта), не забудь указать настройки ID хостинга (т.е. названия твоей домашней директории) и реквизиты для подключения к mysql базе примерно в 84 строке скрипта (+ там же проверка на наличие итогового бэкапа бд):

#!/bin/bash
## @author    Samoylov Nikolay
## @project   Nic.ru backup script
## @copyright 2014 
## @github    https://github.com/tarampampam/nic.ru-bascup-script/
## @version   0.1.3
##
## @depends   mysqldump, tar

# *****************************************************************************
# ***                               Config                                   **
# *****************************************************************************

## nic.ru hosting id, look in 'cd ~ && pwd', ex.:
## [[email protected] ~]$ cd ~ && pwd
## /home/%YourID%
HostingID="your_domain";
## Path to home dir, not need in change
PathToHomeDir=/home/$HostingID
## Path to directory, where backups will stored
PathToBackupsDir=$PathToHomeDir/backups
## Path to directory, where store DataBase dumps (add to backup file, and
##   remove from file system), not need in change
PathToDatabaseDumps=$PathToHomeDir/database-backup
##
## !!! IMPORTANT !!!
## Add your login, password and db_name to 'mysqldump' (line ~84)
## !!! IMPORTANT !!!
##
## Days count for backup files store, not need in change
StoreBackupsDaysCount=31

# *****************************************************************************
# ***                            END Config                                  **
# *****************************************************************************

## Found here - http://goo.gl/4Oi5ZK
cRed='e[1;31m'; cGreen='e[0;32m'; cNone='e[0m'; cYel='e[1;33m';
cBlue='e[1;34m'; cGray='e[1;30m'; cWhite='e[1;37m';

## Helpers Functions ##########################################################

logmessage() {
  ## $1 = (not required) '-n' flag for echo output
  ## $2 = message to output

  flag=''; outtext='';
  if [ "$1" == "-n" ]; then
    flag="-n "; outtext=$2;
  else
    outtext=$1;
  fi

  echo -e $flag[$(date +%H:%M:%S)] "$outtext";
}

## Begin work #################################################################

# Create directory for backups (if not exists)
if [ ! -d $PathToBackupsDir ]; then
  logmessage -n "Create $PathToBackupsDir.. ";
  mkdir -p $PathToBackupsDir;
  if [ -d $PathToBackupsDir ]; then
    echo -e "Ok";
  else
    echo -e "Error";
    exit 1;
  fi
fi

# Clean temp dumps $PathToDatabaseDumps + create it (ex: broken last run)
logmessage -n "Clean and prepare $PathToDatabaseDumps.. ";
rm -R -f $PathToDatabaseDumps;
mkdir -p $PathToDatabaseDumps;
if [ -d $PathToBackupsDir ]; then
  echo -e "Ok";
else
  echo -e "Error";
fi

logmessage -n "Backup DataBase(s) to $PathToDatabaseDumps.. "
mysqldump --force --opt --add-locks --user=your_domain_mysql -pXXXXXXX --databases your_domain_db > $PathToDatabaseDumps/your_domain_db.sql
# Write here all files to check exists
if [ -f $PathToDatabaseDumps/your_domain_db.sql ]; then
  echo -e "Complete";
else
  echo -e "Error";
fi


cd $PathToBackupsDir
thisBackupFileName=backup-$(date +%y-%m-%d--%H-%M)-$HostingID.tar.bz2

logmessage -n "Pack files to $PathToBackupsDir/$thisBackupFileName.. "
tar -cpPjf $PathToBackupsDir/$thisBackupFileName \
    --exclude=$PathToBackupsDir* \
    --exclude=$PathToHomeDir/tmp/* \
    --exclude=*httpd.core \
    $PathToHomeDir;
echo -e "Complete";

# Make some clean
logmessage -n "Make some clean.. ";
rm -R -f $PathToDatabaseDumps;
echo -e "Complete";

sleep 2;

## Finish work ################################################################

logmessage -n "Deleting old backups from $PathToBackupsDir.. "
find $PathToBackupsDir -type f -mtime +$StoreBackupsDaysCount -exec rm '{}' \;
for FILE in $(find $PathToBackupsDir -mtime +$StoreBackupsDaysCount -type f); do
  logmessage "Deliting $FILE as Old";
  rm -f $FILE;
done;
echo -e "Complete";

По итогу его выполнения у нас должна появиться директория ~/backups с текущим бэкапом всех данных. Для автоматизации в «Планировщик заданий» добавь задание вида /home/your_domain/scripts/backup.sh с запуском 1 раз в полночь, например.

На этом в принципе — всё. Если будут вопросы — можешь смело задать их в комментариях.

  1. Добрый день!
    Делаю все, как Вы сказали, но при попытке загрузки блога выдает:

    Похоже, в вашей конфигурации PHP отсутствует расширение MySQL, необходимое для работы WordPress
    • Привет! Извини за поздний ответ.
      Если проблема уже решена то, пожалуйста, расскажи как именно ты это сделал. Если же нет — то думаю есть смысл проверить наличие строк в файле ~/etc/php53.ini:

      extension=pdo_mysql.so
      extension=mysql.so
      mysql.default_port=3306
      mysql.default_host=%your_uname%.mysql

      p.s. php 5.3 очень старый, и необходимо переходить на php 5.6

Комментирование наглухо закрыто