Mail-in-a-Box: access Nextcloud from a different subdomain (or domain)

I like having MiaB’s various software set up on different subdomains. It looks nicer and appeases the Gods of OCD.


Mail-in-a-Box is a fantastic and easy-to-deploy mail server that comes with a suite of different, intermingled software that comes preinstalled during initial set up on your server.

One of the biggest positives of MiaB also comes with one of its biggest negatives:

Upside

  • Installing Mail-in-a-Box is quick and easy, and it comes with some great features out of the box. It’s virtually a set-it-and-forget-it project for its end users.

Downside

  • Mail-in-a-Box is not necessarily geared toward power users. What you get is what you get, and customizing it means having no official support. Customizing your box also means having to redo your changes (depending on what you’re doing).

Thankfully, there are certain things that you’re able to do without having to worry about them being overwritten during updates. One of those things is setting up Nextcloud to be accessible through a different subdomain.

On a traditional and recommended set up, your MiaB installation would be located at box.example.com. This means Nextcloud would be found at box.example.com/cloud; perfectly suitable and nothing wrong with that.

However, there are those of us that might have our installations set up with a different subdomain — ours here at Obstance, for example, is set up at mail.obstance.com.

In that instance, the various software that gets set up automatically by Mail-in-a-Box tends to make less sense semantically. I decided to set up Munin at metrics.obstance.com and Nextcloud at drive.obstance.com.

In this article, I’ll walk you through setting up Nextcloud on its own subdomain. You’ll also be able to follow the steps using an entirely different domain name.

Nginx Configuration for Nextcloud on a subdomain

  1. SSH into your box (SFTP works well too). Navigate to /etc/nginx/conf.d
  2. Create a conf file and name it anything. We’ll call it nextcloud-subdomain.conf. Paste the contents below into that file, but change drive.example.com to whatever (sub)domain you want to use. That’s in the server_name and root directives.
# Serve NextCloud from a different subdomain
server {
    listen 80;
    listen [::]:80;

    server_name drive.example.com;

    root /home/user-data/www/drive.example.com;
    index index.html index.htm;

    # Hide Nginx version
    server_tokens off;

    # Nextcloud configuration.
    rewrite ^/cloud$ /cloud/ redirect;
    rewrite ^/cloud/$ /cloud/index.php;
    rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
    rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
    rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
    location /cloud/ {
        alias /usr/local/lib/owncloud/;
        location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
            deny all;
        }
        location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
            deny all;
        }
        # Enable paths for service and cloud federation discovery
        # Resolves warning in Nextcloud Settings panel
        location ~ ^/cloud/(oc[sm]-provider)?/([^/]+\.php)$ {
            index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$1/$2;
            fastcgi_pass php-fpm;
        }
    }
    location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
        # note: ~ has precendence over a regular location block
        # Accept URLs like:
        # /cloud/index.php/apps/files/
        # /cloud/index.php/apps/files/ajax/scan.php (it's really index.php; see 6fdef379adfdeac86cc2220209bdf4eb9562268d)
        # /cloud/ocs/v1.php/apps/files_sharing/api/v1 (see #240)
        # /cloud/remote.php/webdav/yourfilehere...
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$2;
        fastcgi_param SCRIPT_NAME $1$2;
        fastcgi_param PATH_INFO $3;
        fastcgi_param MOD_X_ACCEL_REDIRECT_ENABLED on;
        fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
        fastcgi_read_timeout 630;
        fastcgi_pass php-fpm;
        client_max_body_size 1G;
        fastcgi_buffers 64 4K;
    }
    location ^~ /owncloud-xaccel/ {
        # This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
        # path on disk as a subdirectory under this virtual path.
        # We must only allow 'internal' redirects within nginx so that the filesystem
        # is not exposed to the world.
        internal;
        alias /;
    }
    location ~ ^/((caldav|carddav|webdav).*)$ {
        # Z-Push doesn't like getting a redirect, and a plain rewrite didn't work either.
        # Properly proxying like this seems to work fine.
        proxy_pass https://127.0.0.1/cloud/remote.php/$1;
    }
    rewrite ^/.well-known/host-meta /cloud/public.php?service=host-meta last;
    rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
    rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
    rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;

    # Necessary for Let's Encrypt Domain Name ownership validation
    location ~ /.well-known {
        location ~ /.well-known/acme-challenge/(.*) {
            add_header Content-Type application/jose+json;
        }
    }

    ####
    ### Restrictions
    ####

    # allow robots.txt to be accessed without physically making the file
    # disallow all bots — metrics site should never be indexed
    location = /robots.txt {
        add_header Content-Type text/plain;
        return 200 "User-agent: *\nDisallow: /\n";
        log_not_found off; # not really needed due to above. No harm though
        access_log off;
    }

    # Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
    # This block is placed at the end. Nginx's precedence rules means this block
    # takes precedence over all non-regex matches and only regex matches that
    # come after it (i.e. none of those, since this is the last one.) That means
    # we're blocking dotfiles in the static hosted sites but not the FastCGI-
    # handled locations for Nextcloud (which serves user-uploaded files that might
    # have this pattern, see #414) or some of the other services.
    location ~ /\.(ht|svn|git|hg|bzr) {
        log_not_found off;
        access_log off;
        deny all;
    }
}

Note:
The above is only for HTTP; we’ll get certificates generated using Certbot to set up HTTPS in a bit. If you’ve already set up your subdomain with HTTPS, then you can skip parts of this tutorial. The same could be said if you’re sticking with HTTP, but nobody recommends doing that, so please don’t.

Now test your Nginx configuration and reload it, but only if there are no errors.

user@box:~$ sudo nginx -t && sudo systemctl reload nginx

DNS: point your Nextcloud subdomain to your mail server

You’ll need to point your domain or subdomain to the same IP address of your box. In cases like this, I prefer to use a CNAME since it’s less work in case your box’s IP address ends up changing in the future. On top of that, there’s no need to add both an A and AAAA record for IPv4 and IPv6 respectively, because a CNAME will redirect to either one of those when appropriate.

drive.example.com.	43200	IN	CNAME	box.example.com.

After you’ve created the Nginx conf and correctly pointed your subdomain or domain to your mail server, check that Nextcloud loads at drive.example.com/cloud. You’ll get a security warning in your browser because we haven’t set up any TLS certificates yet, but continue through since it’s safe to do so.

When you’ve accepted the “risks” in your browser, you should now see the Nextcloud page. It will have a warning stating “Access through untrusted domain.” This is normal, and in the next step we’ll create the config file to allow the subdomain to be used with Nextcloud.

Create a custom config so that the subdomain is trusted by Nextcloud

Nextcloud lets us create multiple config.php files, which is perfect for our situation. Mail-in-a-Box manages the default file, which is located at /home/user-data/owncloud/config.php. We don’t want to edit this directly, because we’d lose our changes after updates.

Instead, create a new file called custom.config.php in the same directory that the original configuration file resides. Paste this into the file and save it:

<?php
/*
 * Custom configuration file as to not have custom
 * changes overwritten after Mail-in-a-Box updates
 */
$CONFIG = array (
  'trusted_domains' =>
  array (
    1 => 'drive.example.com',
  ),
);

Again, remember to change drive.example.com with the proper domain.

Now symlink the new config file and give it the proper permissions so that Nextcloud can read it:

user@box:~$ sudo ln -s /home/user-data/owncloud/custom.config.php /usr/local/lib/owncloud/config
user@box:~$ sudo chmod 600 /home/user-data/owncloud/custom.config.php; sudo chown -h www-data:www-data /usr/local/lib/owncloud/config/custom.config.php

Visit drive.example.com/cloud again to make sure that the untrusted warning is gone and you have access to Nextcloud. Once you’ve done that, you can start finishing up.

Place an index.html file in your public folder

Whatever you put into the root directive of your Nginx file, place an index.html file into that directory (/home/user-data/www/drive.example.com).

This will be the landing page for drive.example.com, where you can either redirect it directly to /cloud or use as a static website — maybe to give additional information like a FAQ or links to your other domains. Here’s an example of what I did:

/home/user-data/www/drive.example.com/index.html:

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta name="robots" content="noindex">
		<script type="text/javascript">
	            window.location.href = "https://drive.example.com/cloud/";
        </script>
		<noscript>
			<meta http-equiv="refresh" content="0;url=https://drive.example.com/cloud/">
		</noscript>
		<title>Example Drive</title>
		<style>
		  body {
			  text-align: center;
			  border-top: 10px solid #f1f1f1;
			  color: #414141;
			  font: 17px Helvetica, Arial, sans-serif;
			  margin: 0;
			  padding: 0;
			  line-height: 1.86;
		  }
		  h1 { font-size: 50px; font-weight: 400; line-height: 1.2; }
		  article { display: block; text-align: left; max-width: 650px; margin: 0 auto; margin-top: 80px; padding: 0 50px 50px; }
		  a { color: #0077bd; text-decoration: none; }
		  a:hover { color: #0282b1; text-decoration: none; }
		</style>
	</head>

<body>
	<article>
		<h1>We'll get you redirected to the right place</h1>
		<div>
			<p>This page is used internally for for mystic purposes.</p>
			<p>In a few seconds, it will redirect you to the public-facing cloud drive. <br>... with magic.</p>
			<p>Oh, but if the magic doesn't work, <a href="https://drive.example.com/cloud/">click here</a>.</p>
		</div>
	</article>
</body>
</html>

This uses JavaScript to redirect you immediately to /cloud. If JavaScript isn’t enabled, it falls back to using a meta refresh, and finally, if that doesn’t work, there’s a link on the styled HTML page that the person can click on.

If you want to see how the page looks before you roll with it (or while you modify it), remove <script> and <noscript> elements entirely and open it in your browser.

Alternatively, replace the JavaScript with a time delay:

		<script type="text/javascript">
		  setTimeout(function(){
		    window.location.href = 'https://drive.example.com/cloud/';
		  }, 15000);
		</script>
		<!-- Also change the 0; but using seconds instead of milliseconds -->
		<noscript>
			<meta http-equiv="refresh" content="15;url=https://drive.example.com/cloud/">
		</noscript>
Simple index.html file for the Miab Nextcloud Subdomain

Generate Let’s Encrypt certificates for the Nextcloud subdomain

Almost done! Generating TLS certificates with Certbot and remodifying the Nginx conf file are the last steps we need to do.

user@box:~$ sudo -H certbot certonly --webroot-path=/home/user-data/www/drive.example.com -d drive.example.com

You can add --dry-run to the command to test it before trying to generate the certificates, but make sure you change drive.example.com in both places before doing so.

Now, go back to /etc/nginx/conf.d/nextcloud-subdomain.conf to edit the file to use your new certificates:

# Serve NextCloud from a different subdomain
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name drive.example.com;

	root /home/user-data/www/drive.example.com;
	index index.html index.htm;

    # Hide Nginx version
	server_tokens off;

    # Nextcloud configuration.
    rewrite ^/cloud$ /cloud/ redirect;
    rewrite ^/cloud/$ /cloud/index.php;
    rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
    rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
    rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
    location /cloud/ {
        alias /usr/local/lib/owncloud/;
        location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
            deny all;
        }
        location ~ ^/cloud/(?:\.|autotest|occ|issue|indie|db_|console) {
            deny all;
        }
        # Enable paths for service and cloud federation discovery
        # Resolves warning in Nextcloud Settings panel
        location ~ ^/cloud/(oc[sm]-provider)?/([^/]+\.php)$ {
            index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$1/$2;
            fastcgi_pass php-fpm;
        }
    }
    location ~ ^(/cloud)((?:/ocs)?/[^/]+\.php)(/.*)?$ {
        # note: ~ has precendence over a regular location block
        # Accept URLs like:
        # /cloud/index.php/apps/files/
        # /cloud/index.php/apps/files/ajax/scan.php (it's really index.php; see 6fdef379adfdeac86cc2220209bdf4eb9562268d)
        # /cloud/ocs/v1.php/apps/files_sharing/api/v1 (see #240)
        # /cloud/remote.php/webdav/yourfilehere...
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$2;
        fastcgi_param SCRIPT_NAME $1$2;
        fastcgi_param PATH_INFO $3;
        fastcgi_param MOD_X_ACCEL_REDIRECT_ENABLED on;
        fastcgi_param MOD_X_ACCEL_REDIRECT_PREFIX /owncloud-xaccel;
        fastcgi_read_timeout 630;
        fastcgi_pass php-fpm;
        client_max_body_size 1G;
        fastcgi_buffers 64 4K;
    }
    location ^~ /owncloud-xaccel/ {
        # This directory is for MOD_X_ACCEL_REDIRECT_ENABLED. Nextcloud sends the full file
        # path on disk as a subdirectory under this virtual path.
        # We must only allow 'internal' redirects within nginx so that the filesystem
        # is not exposed to the world.
        internal;
        alias /;
    }
    location ~ ^/((caldav|carddav|webdav).*)$ {
        # Z-Push doesn't like getting a redirect, and a plain rewrite didn't work either.
        # Properly proxying like this seems to work fine.
        proxy_pass https://127.0.0.1/cloud/remote.php/$1;
    }
    rewrite ^/.well-known/host-meta /cloud/public.php?service=host-meta last;
    rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
    rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
    rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;

    ####
	### SSL and SSL Optimizations
	####

    # Necessary for Let's Encrypt Domain Name ownership validation
    # Certs are generated via command line, using
    # sudo -H certbot certonly --webroot-path=/home/user-data/www/drive.example.com -d drive.example.com
	location ~ /.well-known {
		location ~ /.well-known/acme-challenge/(.*) {
			add_header Content-Type application/jose+json;
		}
	}

    # In default /etc/letsencrypt location (not in /home/user-data/ssl)
    ssl_certificate /etc/letsencrypt/live/drive.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/drive.example.com/privkey.pem;

    ### ssl_ciphers, ssl_dhparam, ssl_session_cache, ssl_session_timeout, stapling and resolvers are in /etc/nginx/conf.d/ssl.conf
    ssl_trusted_certificate /etc/letsencrypt/live/drive.example.com/chain.pem;

    # ssl files sha1: 9dc67cb167738c5c712137dd8513b9457c437f14 / 4708e05e578813027dfbae359895d69c3c5d95e9
	add_header Strict-Transport-Security "max-age=15768000" always;

    ####
	### Restrictions
	####

    # NextCloud folder already has robots.txt file,
    # but it is under the /cloud/ directory. Use this
    # to load into the root of the subdomain
    location = /robots.txt {
        add_header Content-Type text/plain;
        return 200 "User-agent: *\nDisallow: /\n";
        log_not_found off; # not really needed due to above. No harm though
		access_log off;
    }

	location = ^/favicon.ico {
		log_not_found off;
		access_log off;
	}

	# Disable viewing dotfiles (.htaccess, .svn, .git, etc.)
	# This block is placed at the end. Nginx's precedence rules means this block
	# takes precedence over all non-regex matches and only regex matches that
	# come after it (i.e. none of those, since this is the last one.) That means
	# we're blocking dotfiles in the static hosted sites but not the FastCGI-
	# handled locations for Nextcloud (which serves user-uploaded files that might
	# have this pattern, see #414) or some of the other services.
	location ~ /\.(ht|svn|git|hg|bzr) {
		log_not_found off;
		access_log off;
		deny all;
	}
} # End 443 server block

# Redirect all HTTP requests to HTTPS
server {
    listen 80;
	listen [::]:80;
    server_name drive.example.com;
    return 301 https://$host$request_uri;
}

There are 8 places where you should replace drive.example.com, so be sure to do so.

Reload Nginx.

user@box:~$ sudo nginx -t && sudo systemctl reload nginx

If you want to redirect your old link at box.example.com/cloud to your new one at drive.example.com/cloud, you can create a custom.yaml file in /home/user-data/www.

This allows you to create an Nginx rewrite without touching the local.conf file, so your changes will persist through Mail-in-a-Box updates.

In your new custom.yaml file, paste these contents and change the domain name:

## Custom YAML ##
# Redirect box.example.com/cloud to drive.example.com/cloud
box.example.com:
  redirects:
    ^/cloud(/.*): https://drive.example.com/cloud$1

The above won’t take effect until you regenerate the configuration. You can do this using either of these two options:

  1. Go to box.example.com/admin#web and click on the “Change” button next to any of the sites, and then click the “Update” button.
  2. Run sudo ~/mailinabox/tools/web_update from the command line using the user you used to set the box up with.

Bonus: get an email alert if your files change

While this method of setting up Nextcloud on a subdomain shouldn’t have anything get overwritten during Mail-in-a-Box upgrades, you still might want to get alerted just in case. Or maybe you’ve altered some core files and need to track those. In either case, setting up monitoring software will do the trick:

One last note. When you upgrade your Mail-in-a-Box installation, everything in the /usr/local/lib/owncloud directory will be overwritten. This means that your symlink for custom.config.php will be deleted.

Whether you placed that config file directly in that folder or symlinked it, thankfully an automatic backup is taken before the upgrade proceeds. Find it in /home/user-data/owncloud-backup. This is not quite as important for a simple config file, but this note may be helpful if you’re customizing your box more fully.

One Response to “Mail-in-a-Box: access Nextcloud from a different subdomain (or domain)”

  1. BernieO

    Hi
    This looks great. I have 2 queries..
    I thought the MIAB people weren’t too hot on people trying to integrate full Nextcloud due to database (SQLite) issues?
    Secondly Can the same be done with apache2 and if so what bits change or have to be added.

    Reply

    Post a comment

    • (will not be published)


    This site uses Akismet to reduce spam. Learn how your comment data is processed.

    XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>