Веб-разработка

индекс
236,88

Динамические поддомены с использованием nginx+apache

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

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

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

Например, у нас есть готовый сайт, на котором работают профили по такому url: www.example.com/users/username, и есть всякие дополнительные возможности (например www.example.com/users/username/contact и другие страницы, связанные с этим юзером).

И мы теперь хотим вынести все, что связано с юзером, на поддомен, например username.example.com/, username.example.com/contact и т.д.)

Решения, которые были найдены в интернете, меня не удовлетворили по 2 причинам:
  • Не нашел решения как заставить ее работать, сохранив работоспособность домена www.example.com
  • Все найденные решения подходят только для перенаправления в папку и не работают если дальше должны работать какие то правила


На нашем сайте стоит nginx над апачем (как и на многих других), поэтому пришлось изобретать велосипед самому, используя эту связку (nginx+ apache, благо сейчас почти на всех крупных сайтах стоит проксирующий nginx над апачем)



В общем то решение простое — т.к. на сайте уже налажена через mod_rewrite работоспособность ссылок вида www.example.com/users/([a-zA-Z_]+) то было принято решение делать рерайт поддоменов через nginx.

Дополнительное условие — наш сайт работает только как ww.example.com, а example.com редиректит на www.

Соответственно осталось просто написать правило в конфиге nginx для рерайта поддоменов. Правило получилось такое это решение — не верное, использовать его не рекомендуется:
location / {
proxy_pass 11.22.33.44:8080;
proxy_redirect www.example.com:8080/ /;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;

# учитываем что обязательно нужен www
set $uid "www";
# берем uid из поддомена
if ($host ~* "^(([a-z0-9\-]+)\.example\.com)$") {
set $uid $2;
}
# рерайтить нужно только если uid не www, на www работает весь сайт
if ($uid !~ "^(www)$") {
rewrite ^(.*)$ /users/$uid$1 break;
}
}

upd После публикации топика BlackWizard подсказал лучшее решение, которое отвечает всем изначальным условиям:
server {
server_name www.example.com;

location / {
proxy_pass 11.22.33.44:8080;
}
}

server {
server_name ~^(?[a-z0-9\-]+)\.example.com$;

location / {
proxy_pass 11.22.33.44:8080/users/$user$uri$is_args$args;
}
}


Таким образом, если посетитель зашел на поддомен то nginx это определяет и запрашивает из апача уже адрес вида www.example.com/users/username, а апач уже дальше разбирает все в соотвествии со своими правилами mod_rewrite.

Полученное в итоге решение обладает следующими плюсами:
  • Нет проблемы с www
  • Легко внедряется на любом сайте с уже готовой системой ссылок (не будем рассматривать процесс измененения ссылок на самом сайте)
  • Работает как для папок, так и для url, которые используют mod_rewrite


Минусы:
  • Требуется проксирующий nginx


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

UPDкак сделать чтобы username.example.com работал без указания всех возможных доменов в конфиге веб-сервера
Чтобы сервер корректно обрабатывал динамические поддомены, необходимо добавить одну маленькую запись в настройки DNS. Это можно сделать, используя панель управления сервером.
Просто добавьте следующую запись формата A ("A record" в англоязычной версии):
*.example.com

Запись нужно добавлять после всех поддоменов (mail, smtp и т.д.)
+48
12 августа 2010, 14:34
252

комментарии (50)

0
Nc_Soft #
есть мысль обойтись одной регуляркой и ифом. Как бы задать такое условие?
if ($host ~* "^(не www)\.example\.com$") {
rewrite…
}
0
rednaxi #
тоже о таком думал, но не смог составить простую регулярку, которая бы это делала.

Поэтому остановился на таком костыле, который позволяет обойти ограничение, что нельзя использовать логический оператор «и» и вложенные if в nginx
0
LMik #
Логику вложенных if можно сделать установкой переменных.

if условие {
set a «a»;
}

if условие2 {
set $aa «a»;
}

if $a==«aa» {
и т. д.
}
0
rednaxi #
я так и сделал вообще-то

set $uid «www»;

if ($host ~* "^(([a-z0-9_\-]+)\.example\.com)$") {
set $uid $2;
}

if ($uid !~ "^(www)$") {
rewrite ^(.*)$ /users/$uid$1 break;
}
+1
maxshopen #
if ($host ~* "([a-z0-9\-]+)(?<!^www)\.example\.com$") {
    rewrite
}
0
maxshopen #
птичку забыл вначале ^([a-z0-9\-]+)(?<!^www)\.example\.com$
0
GriGor1Z #
Спасибо, добавил в израбнное на будущее.
0
xdevel #
Спасибо, сейчас стоит такая задача — подыскиваю наиболее подходящий вариант решения.
0
LMik #
Это же достаточно элементарно. А вот про минусы не понял :/, вы раньше апачем напрямую всё отдавали?
0
rednaxi #
Я нет, но есть проекты, где только апач без проксирующего nginx.

Я не спорю что это элементарно, но решения в таком виде нагуглить не удалось, поэтому решил оставить на будущее — вдруг кому пригодится.
0
LMik #
Т.е для вас это плюс, а вы пишите минус? :)
0
rednaxi #
Для меня это не минус и не плюс, а просто использование возможностей того ПО, которое у меня есть.

Для кого-то это может оказаться минусом, потому что придется устанавливать и конфигурировать доп. ПО
+3
mgn #
Еще надо учесть, что нижнее подчеркивание нельзя использовать в поддоменах.
Еще одна статья в тему
Пользовательские поддомены
0
rednaxi #
собственно, мое решение основано на этой статье, только там не решена проблема с www — там делается редирект, а я предложил способ, как сохранить работоспособность www

Про нижнее подчеркивание — исправил регулярку в статье
0
onthefly #
можете аргументированно пояснить, почему нельзя?
0
mgn #
Ответ из старого топика из обсуждений

«Интересные имена со знаком „_“ :) Особенно в поддоменах. IE не берет с такого домена куки, а опера ругается, что введенный урл не соответствет стандартам (потому что не соответствует) и не грузит страницу совсем. „

Сейчас попробовал набрать
www_123.ya.ru/
www-123.ya.ru/

По первому адресу 400 Bad Request
По второму — 404
0
onthefly #
С яндексом пример некорректен. Что касается утверждения выше, то видно, что вы самостоятельно его не проверяли.

Есть много работающих ресуров, где с указанными браузерами описанных проблем не наблюдается. Например, модуль субдоменов для движка Livestreet установлен на множестве сайтов, ни от кого не поступало таких жалоб да и тестирование также не выявило никаких проблем кроме эпизода с написанием доменов в разных регистрах (сафари).

Я не знаю, о каких «стандартах» говорил тот хабраюзер, но если открыть RFC1738 (Uniform Resource Locators), то можно прочесть следующее:
Thus, only alphanumerics, the special characters "$-_.+!*'(),", and
reserved characters used for their reserved purposes may be used
unencoded within a URL.

Вы можете указать, какой конкретно стандарт запрещает использовать символ "_" для субдоменов?
0
maxshopen #
Не путайте URL и Domain Names. В URL "_" допустим, в доменном имени — нет. Конкретно стандарт RFC1035, цитирую:
2.3.1. Preferred name syntax
<domain> ::= <subdomain> | " "
<subdomain> ::= <label> | <subdomain> "." <label>
<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
<let-dig-hyp> ::= <let-dig> | "-"
<let-dig> ::= <letter> | <digit>
<letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
<digit> ::= any one of the ten digits 0 through 9


The labels must follow the rules for ARPANET host names. They must
start with a letter, end with a letter or digit, and have as interior
characters only letters, digits, and hyphen
. There are also some
restrictions on the length. Labels must be 63 characters or less.


–1
onthefly #
2.3.1. Preferred name syntax


Я не увидел здесь прямого запрета, а вы? Предпочтительный синтаксис не означает строгого следования этим правилам. С документом ошибся, вы правы.
0
maxshopen #
Ок, есть еще RFC1123 «Requirements for Internet Hosts». Читаем:
2.1 Host Names and Numbers
The syntax of a legal Internet host name was specified in RFC-952 [DNS:4].
Открываем RFC-952:
ASSUMPTIONS
1. A «name» (Net, Host, Gateway, or Domain name) is a text string up
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
sign (-), and period (.)
. Note that periods are only allowed when
they serve to delimit components of «domain style names». (See
RFC-921, «Domain Name System Implementation Schedule», for
background). No blank or space characters are permitted as part of a
name. No distinction is made between upper and lower case. The first
character must be an alpha character. The last character must not be
a minus sign or period.

Достаточно или еще гуглом для вас поработать? ;)

Все эти стандарты в некотором смысле рекомендательные, т.е. нет службы полицаев, которые посадят или оштрафуют за то, что вы им не следуете. Так что всегда находятся люди, которые вертели на стержне все эти RFC и рекомендации как делать предпочтительно, им пофиг, у них своё видение как всё должно работать. Слава богу их меньшинство, иначе интернета могло бы и вовсе не быть в том виде, в котором мы его знаем, а были бы кучки разрозненных анклавов.
0
onthefly #
Спасибо за потраченное время, но документы 1985 года в наши дни выглядят несколько архаично. Не исключено даже, что есть какие-то более новые документы, регулириующие эти вопросы. Я попробую вечером их найти.

Исходя из ваших слов, их на стержне вертит даже ICANN, что доказывает вот этот домен, который серьёзно превышает лимит в 24 символа: thelongestlistofthelongeststuffatthelongestdomainnameatlonglast.com
0
maxshopen #
В том же RFC1035 указано не 24 символа для имени а 63 для сегмента (label), т.е. этот лимит расширили. Это прям в моем предыдущем комментарии написано. Так что ICANN никого не вертит.
0
onthefly #
Сегменты с вхождением "_" обнаружить нетрудно:

$ dig beta._domainkey.google.com TXT
$ dig beta._domainkey.google.com TXT
$ dig beta._domainkey.google.com TXT
$ dig _spf-a.microsoft.com TXT
$ dig _domainkey.cern.ch TXT


Получается, вертят крупнейшие игроки IT? Да, это не записи типа A, но всё же полноправные labels.

0
onthefly #
$ dig _domainkey.ebay.com TXT
$ dig _domainkey.yahoo.com TXT
$ dig _domainkey.yandex.ru TXT
0
onthefly #
Вот и стандарт от 2000 года нашёлся, который это дело разрешает: www.ietf.org/rfc/rfc2782.txt
0
maxshopen #
Я не конца понял о чем этот стандарт, но судя по смыслу в нем предлагается использовать _ для специальных целей, что скорее даже подчеркивает тот факт, что _ нельзя использовать просто так от балды. Тут как бы сразу два нарушения — сам факт использования подчерка и расположение его на первом месте, где может быть только буква или цифра. Т.е. с таким же успехом они могли взять $, который тоже не разрешен в именах доменов или % и это бы не означало, что его теперь можно использовать где угодно.
Мутная тема ;)
0
onthefly #
Я привёл это в качестве развенчания мифа, что "_" нельзя использовать в сегментах (узлах) DNS. Как видно, он широко используется в Sender Policy Framework.
+1
maxshopen #
В общем мне очень понравилась наша с вами дискуссия, весьма конструктивная я считаю. Можно немного подытожить:

1. Нифига не понятно :)
2. Есть рекомендации по синтаксису доменных имен, в них "_" не рекомендован, во избежание проблем с клиентским софтом (так сказано в RFC1035)
3. Есть старые документы, прямо запрещающие использование "_" в именах хостов, доментов и т.п.
4. Есть более новые документы, разрешающие "_" в строго определенной позиции(первым символом) и в специальных DNS-записях, т.е. это алиасы на сервисы.
5. Есть чисто прикладные проблемы связанные в "_". Насколько я помню, squid, например, возможно не во всех версиях, попросту не пропускает через себя клиентов с запросами на такие домены. Админы на работе с этим сталкивались, это реальный случай.

Итого, не знаю как вы, но я сделал вывод, что все таки "_" лучше не использовать, и кстати я не вижу никаких проблем, чем дефис хуже подчерка, непонятно.

Спасибо вам за интересную беседу ;)

+1
onthefly #
Можно использовать для сайтов NSFW. За пример со сквидом спасибо, изучу этот вопрос
0
maxshopen #
Если узнаете что-нибудь новое — дайте знать
0
maxshopen #
К тому же есть практическая сторона. Rucenter не позволит вам зарегистриовать доменное имя с "_" и если попробуете проверить такое доменное имя на свободность, выдаст ошибку:
Название домена должно состоять более чем из одного символа, начинаться и заканчиваться буквой латинского алфавита или цифрой. Промежуточными символами могут быть буквы латинского алфавита, цифры или дефис.
Т.е. для регистратора это правило очевидно (если знаете какого нибудь регистратора, который позволяет — напишите). Понятное дело, что домен второго уровня ничем принципиально от третьего (четвертого, пятого...) не отличается и правила должны быть едиными.
0
onthefly #
Мы сейчас говорим о субдоменах, то есть о сегментах четвёртого уровня в иерархии DNS. Контролируя домен второго уровня, вы можете создать произвольный субдомен без участия регистратора.
0
onthefly #
Регистрацию доменов в зоне .ru регулирует регламент, который запрещает всё, кроме a-z,0-9,-
Регалмент может быть основан на стандартах, но он никоим образом не может диктовать владельцу домена, какие ему создавать и поддерживать субдомены.
0
maxshopen #
Вы хотите сказать что по чистой случайности этот регламент совпадает с «предпочтениями» в RFC1035? ;)
0
onthefly #
Ни в коем случае ;)

Неясно лишь, почему нельзя использовать то, что на самом деле успешно работает.
–1
ZloiZmei #
ISPmanager нормально работает с nginx и без там это делается в 2 клика:

Выбираем нужный домен — добавить запись-

Имя: *
Тип: A
Адрес: Ip адрес сайта

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

Остальное — работа самой CMS, правила в .htaccess.

И будет хоть username.example.com, хоть username.users.example.com, хоть super-article.news.example.com
0
rednaxi #
как раз правила в .htaccess это самое интересное, dns прописать дело 2 минут
0
ZloiZmei #
Смотря как CMS сделана, и для чего требуются динамические поддомены. .htaccess и тот не всегда будет нужен — можно обойтись и вообще без его изменения. Только с CMS немного повозиться, что бы ссылки автоматом заменяла и соображала что показывать на главной странице поддомена, когда больше никаких параметров в url не задано.
0
zaregan #
А смысл?.. Я вот тоже хотел предложить использовать А или CNAME.
0
rednaxi #
Ну я имею в виду что добавление A записи *.example.com — дело 2 минут, а вот наладить сервер чтобы работали все рерайты как надо для поддоменов — уже сложнее (особенно если надо не просто рерайтить поддомен в папку а для поддомена сделать чтобы были работоспособны все человеко-понятные адреса страниц, которые уже есть на сайте, т.е. сделать рерайт вида username.example.com => example.com/users/username => example.com/users.php?page=about&name=username)

Мое решение позволило мне вынести на поддомен довольно большое количетсво страниц (страница с контактами, анкета, еще несколько), приэтом не переписывая заново все рерайты в .htaccess а просто добавив несколько строк в конфиг nginx
0
aleksandro #
Дописали, бы в статье своей что нужно еще сделать для того чтоб username.example.com работал без указания всех возможных доменов в конфиге веб-сервера.
+11
BlackWizard #
Статья как делать НЕ НАДО — if,rewrite это зло.

Правильное решение:

server {
    server_name   www.example.com;

    location / {
         proxy_pass 11.22.33.44:8080;
    }
}

server {
    server_name   ~^(?<user>[a-z0-9\-]+)\.example.com$;

    location / {
        proxy_pass 11.22.33.44:8080/users/$user$uri$is_args$args;
    }
}
0
rednaxi #
Спасибо, за совет, завтра с утра проверю ваш вариант.
0
symbix #
Этот вариант работает и он правильный. А за использование вашего надо бить по рукам ;)
0
rednaxi #
ну я первый раз в жизни конфигурировал nginx :)

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

Топик обновил.
0
orloffkirill #
А просто example.com как обработать?
+1
Nc_Soft #
думаю как-то так
server {
server_name www.example.com, example.com;
либо отдельно
0
orloffkirill #
Теперь в избранное.
Спасибо.
+1
TEHEK #
А вас не беспокоит, что, к примеру, у вас остаются рабочими обе ссылки:
example.com/user
и
user.example.com/

И для них URL для ссылок, стилей и скриптов могут указывать на разные файлы.
<a href="/contacts.html">контакты</a> — /var/www/contacts.html | /var/www/example.com/user/contacts.html

0
rednaxi #
Не беспокоит, у меня настроен permanent redirect с example.com/user на user.example.com/
Дело в том, что у на нашем сайте выносить свой аккаунт на поддомен могут только платные пользователи, поэтому бесплатным доступен адрес example.com/user, а платные могут выбрать себе урл и получить адрес user.example.com/ с перманентным редиректом со старого адреса, чтобы сохранить все страницы в поиске.

Ссылки не волнуют, они генерируются автоматически, и являются не относительными, а абсолютными.

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