Accessing nextcloud on local network

I have nextcloud set up using the nixos service and I can access it via localhost on my host computer but I cannot access it by my host’s internal IP address (even from my host). I’m new to this stuff but I thought localhost was an aliases for the computer’s internal IP so how could one work but not the other? It’s also worth mentioning I have tried hosting other sites that I can access from other devices on the network through the internal IP so confusion about what address to use doesn’t seem to be the problem.

I have a few questions but the main goal is accesing nextcloud from another device on my local network.

Following the wiki, I can get nextcloud running on localhost:8080 with:

services.nextcloud = {
  enable = true;
  package = pkgs.nextcloud24;
  hostName = "localhost";
  config.adminpassFile = "${pkgs.writeText "adminpass" "test123"}";
};

services.nginx = {
  enable = true;
  virtualHosts."localhost" = {
    listen = [{
      addr = "127.0.0.1";
      port = 8080;
    }];
  };
};

I’ve tried messing with hostName to use the internal IP instead. (To make things a little more convoluted, my router’s DNS is set to nextdns, I have a rewrite rule there to send nextcloud.home → internal IP). Not knowing what I’m doing with nginx I decided to press on blindly. So I changed hostName to "nextcloud.home" and altered nginx config to:

services.nginx = {
  enable = true;
  virtualHosts."nextcloud.home:8080" = {
    locations."/".proxyPass = "http://127.0.0.1";
  };
};

At this point I can get to nextcloud on the host computer with the url nextcloud.home but this still only works on the host computer. Which leads to a few questions:

  1. If nextcloud.home is being rewritten to the internal IP of the host across the whole network, why is this only working on one computer? All devices should be being sent to the same place. The only reason I could think of is if nginx is sending all devices to their own 127.0.0.1 but that doesn’t seem to make sense and replacing the value of proxyPass with the internal IP of the host still has the same problem (can access from the host only).
  2. Why does this only work with the port on the virtual host name and not on the proxy pass address? This seems backwards, doesn’t this say it’s passing “nextcloud.home:8080” → “http://127.0.0.1” yet it seems to be passing “nextcloud.home” → “http://127.0.0.1:8080” being that I can access it via nextcloud.home but not nextcloud.home:8080. Since I’m hosting different sites, I don’t want the internal IP address to go straight to port 8080 I would rather have <internal IP>:8080 go to nextcloud.

It would be really nice if I could have multiple rewrite rules for my nextdns that all went to internal IP then said “nextcloud.home” → “http://127.0.0.1:8080”, “jellyfin.home” → “http://127.0.0.1:8096”, etc but with how it’s set up now, if I go to “jellyfin.home” it takes me to a nextcloud warning page saying I’m accessing nextcloud through an untrusted domain. Is there anyway to set this up without registering any domains? The sites are only intended to be accessed locally.

You have some misconceptions about IP, nextcloud, its nixos module and nginx.

Firstly, nextcloud is just a bunch of php files. It needs a running webserver to do anything, and the nixos module uses nginx for this purpose: nixpkgs/nextcloud.nix at cb8d3fe07d3592e5d846bce5f7cb5397efe6b206 · NixOS/nixpkgs · GitHub

I.e., the nixos module already sets up a virtual host for you, and you don’t have to do anything at all with nginx yourself :slight_smile:

Just set services.nextcloud.hostName to “nextcloud.home” and it will all magically work.


It would be really nice if I could have multiple rewrite rules for my nextdns that all went to internal IP then said “nextcloud.home” → “http://127.0.0.1:8080”, “jellyfin.home” → “http://127.0.0.1:8096”, etc

You’re close to achieving this :slight_smile: Just make your DNS route home to the host running nginx, and then set:

services.nginx = {
  enable = true;
  virtualHosts."<dontdothisfornextcloudspecifically>.home" = {
    locations."/".proxyPass = "http://127.0.0.1:8080";
  };
  virtualHosts."jellyfin.home" = {
    locations."/".proxyPass = "http://127.0.0.1:8096";
  };
};

Just keep in mind that nextcloud is a special case because it isn’t actually a web server, but just a bunch of template files intended to be hosted by a real web server.

A lot of other nixos modules allow you to just ask them to configue a virtual host as well (but not jellyfin): NixOS Search


That said, you’re doing some amusing things and I think you’d appreciate knowing just how silly they are. So let me explain:

I’m new to this stuff but I thought localhost was an aliases for the computer’s internal IP so how could one work but not the other?

Firstly, localhost is not that. Try cat /etc/hosts - that file sets locally defined hostnames, and typically contains an entry for 127.0.0.1 localhost, which is what localhost acts as an alias of. It’s essentially a locally defined domain name.

127.0.0.1 is a so-called “loopback” address, which only exists on the host itself. Binding a service to that address means that only requests from localhost will reach the service, because your service will not be listening on an IP address reachable from outside.

If you want to bind a service such that it is accessible from outside, you typically should bind it to 0.0.0.0, which will make it listen on any IP address, including 127.0.0.1 and the address your router hands to you using dhcp.

Using 0.0.0.0 over your intranet IP has the added advantage that the kernel will make this address available very early on without any additional configuration, since it doesn’t have to wait for dhcp negotation to allow a service to bind to it. This way your service can start before the network is up.

When using nginx however, you probably want to listen on 127.0.0.1, and let nginx forward requests to that service with proxyPass. That way only nginx is on the public interface, which is cleaner.


Next up, let me explain what nginx is. Nginx can listen to any ip address or port on your computer, and it has configuration that allows inspecting http requests to respond to them depending on their contents.

You can watch for certain headers, source IP addresses, target IP addresses, ports, etc., and depending on those respond with either different responses (nginx knows how to respond with file contents, as well as a couple of other things, as a fully featured http server), or by forwarding the request to another web server (in this case we refer to it as a “reverse proxy”).

“Virtual hosts” are simply the rule that allows splitting your requests based on the hostname the http request contains. This way you can have a single http server that responds to both home and nextcloud.home, if your DNS routes both of those domain names to the same IP address.

If you understand that, it might be dawning on you why I find your configuration so funny:

services.nginx = {
  enable = true;
  virtualHosts."localhost" = {
    listen = [{
      addr = "127.0.0.1";
      port = 8080;
    }];
  };
};

Here you are asking nginx to listen to requests to ip address 127.0.0.1 on the port 8080, and serve things if it is accessed using the domain “localhost”. This means that you can access nginx using

curl http://localhost:8080

This will give you a pretty website telling you that nginx is running. It will not host anything nextcloud related, though. Just the nginx example page.

If you use

curl http://127.0.0.1:8080

You should get something unexpected, because that doesn’t specify a domain name. You might get nextcloud, depending on the order of the virtualhosts, since the nextcloud service sets up a virtualhost as well.

I think nginx serves the donains in listed order if there is no suitable domain name, so this will work on accident depending on how the nix module writes the configuration file, but not because your configuration makes any sense.

Confusion about “virtual hosts” aside, you’re not asking nginx to serve nextcloud here in the first place. You’re just asking it to respond to http requests with its default page. The second example actually sets up proxyPass, and is somehow both closer and further to making sense:

I’m surprised that nix allows that, and that the service is still running.

You’ve defined a domain name “nextcloud.home:8080”, which to my knowledge is an invalid domain name. nginx is listening for requests to port 80 on all addresses (this is the default), and will serve them if they specify the domain name “nextcloud.home:8080”.

For illustration, you could trigger a request there like so, if this is indeed a valid domain name:

curl http://nextcloud.home:8080:80

Very creative :slight_smile:

If a request does reach this virtual host somehow, it will forward the request to any service listening on port 80 on IP 127.0.0.1, which is nginx itself, just without a domain name.

I believe that nix sorts these virtual hosts alphabetically, so you should end up serving nextcloud in this case again. You do so for all domains, too, so whether this virtual host is reachable or not doesn’t actually matter :wink:

Ah, I missed this bit. Can you actually ping the home server using its domain name? ssh to it? This sounds like your DNS configuration isn’t working.

Since hostName=nextcloud.home, nextcloud will complain if you access it through any other domain name and as explained, currently your nginx will respond to any domain with serving nextcloud. You don’t have to register a domain through a registrar, but if you’d like to host from an IP address with different ports you’ll need to change some settings to disable that warning.

Thanks a lot, I’ve been reading up on this stuff and having a lot of difficulting figuring out what each part does.

Starting off with messing with nginx in the first place. The virtualHosts."localhost".listen bit is actually what’s on the wiki (at the end) for handling the case where port 80 is already in use. I have another service listen on 80 which had been causing problems. It’s not needed when I configure the proxyPass option though (not entirely sure why, I tried removing the listen after adding the proxy pass and it worked). I see what you’re saying about it not making sense since nginx is looking for that exact url so it doesn’t work with 127.0.0.1 which would definitely be unexpected behavior to a user.

Also I changed the virtual host settings to how you put it where the port is on the right address. This makes more sense to me but for some reason it wasn’t working before until I flipped the ports through random guess and check. It may have been related to the listen configuration that I no longer have?

This change does work with jellyfin as well so I can now access nextcloud via nextcloud.home and jellyfin via jellyfin.home.

Ah, I missed this bit. Can you actually ping the home server using its domain name? ssh to it? This sounds like your DNS configuration isn’t working.

Yes, I have other sites (like jellyfin) hosted on the same computer that I can connect to from other devices. But the above changes to nginx narrow done on the issue. I cannot access jellyfin on another computer through jellyfin.home but I can use <internal IP>:8096 on another computer. Another factor jellyfin is running in a docker independent of NixOS. So it looks like the issue might be in how nginx is set up. Is nginx only handling requests made on the host computer? Is this where I should be using 0.0.0.0?

I’ll take a closer look at how nginx works. I assume I’m just missing a step to connect it to my DNS or router.

edit: Took another look at what you said about 0.0.0.0 and this is not where I’d use that since I want nginx to handle it instead.

Quick update: if I remove the rewrite rule in nextdns’s settings nextcloud.home still gets forwarded to the nextcloud instance. So these rules are all acting locally before being sent out to the dns. Then I need to get requests from other devices forwarded to nginx.

Got it. The above comment was incorrect it just took awhile for the network to update after removing the previous rewrite rules in nextdns. I believe the problem was that I had a rule to rewrite *.server.home to the server’s intranet IP and a rule to rewrite *.home to the server’s intranet IP but I’m not sure. There’s nothing specific I changed that got it working since my previous update.

Final config:

  services.nextcloud = {
    enable = true;
    package = pkgs.nextcloud24;
    hostName = "nextcloud.home";
    config.adminpassFile = "${pkgs.writeText "adminpass" "test123"}";
  };

  services.nginx = {
    enable = true;
    virtualHosts."nextcloud.home" = {
      locations."/".proxyPass = "http://127.0.0.1:8080";
      extraConfig = ''
        allow 192.168.0.1/24;
        deny all;
      '';
    };
    virtualHosts."jellyfin.home" = {
      locations."/".proxyPass = "http://127.0.0.1:8096";
      extraConfig = ''
        allow 192.168.0.1/24;
        deny all;
      '';
    };
  };

I don’t believe the extraConfig does anything since it can only be accessed on the local network anyway but might as well have it.

Edit: The problem was I didn’t open up ports in firewall. The fix was adding networking.firewall.allowedTCPPorts = [ 80 443 ]; in my host`s config. Can confirm that it stops working when this line is commented out.

Note that this bit of configuration either does nothing or breaks your configuration. It’s conflicting with this from the NixOS module you use: nixpkgs/nextcloud.nix at cb8d3fe07d3592e5d846bce5f7cb5397efe6b206 · NixOS/nixpkgs · GitHub

Since you’re overriding what / points to with a proxyPass, nextcloud simply should not be working. Do you have a docker container with nextcloud in it running on port 8080? That’s the only way I understand your configuration working right now.

If you already have a service listening on port 80 this shouldn’t work at all, since nginx also listens on port 80. How else should it be able to pass http requests to internal services? If you use a nixos module for that, perhaps it’s also configured to use a virtual host, and that’s why that is working? Hell, if you already had a service on port 80, how did that function without the firewall being opened?

If you want to change the ports nginx listens on, you need to set services.nginx.virtualHosts..listen.*.port, and then access your services with e.g. jellyfin.home:8096.

It doesn’t override the "/" it just adds another attribute right? services.nginx.virtualHosts.${cfg.hostname}."/" goes from:

{
  priority = 900;
  extraConfig = "rewrite ^ /index.php;";
};

to:

{
  priority = 900;
  extraConfig = "rewrite ^ /index.php;";
+ proxyPass = "http://127.0.0.1:8080";
};

Or is that not how it works here? If I understand correctly, attribute sets are like environments where nix searches for name-value pairs from most recent attribute set then pops up to an earlier set until it finds the name it’s looking for.

That being said extraConfig is overriding some important stuff so removing that.

There’s no other instance of nextcloud on my computer. I tried setting it up as a docker then gave up and went to nix before I got it running.

As far as the ports go I have no idea. When I was trying to get nextcloud-aio running in a docker, running without modification lead to an error that there was already a service running on port 80 (this was before adding nginx, I think it’s my nextdns service). I then followed the instructions on the nextcloud-aio wiki for getting around this through using caddy (which I’ve since removed) and that error went away. Pretty positive the firewall was my issue for getting the docker up too (especially with how many places in the wiki it says to make sure prots 80 and 443 are open :slight_smile: ).

Ports and port forwarding seemed like an easy enough concept but after working on some docker services and this over the last couple weeks, it’s clear I don’t really understand them so I have limited ability to comment on why it’s working now. I thought maybe the other service might have opened up port 80 in the firewall and I just needed to allow port 443, but doubled checked and if I don’t explicitly open port 80 it doesn’t work. My only guess is maybe I was getting the error that 80 was already in use because the firewall was blocking it rather than another service was using it and whatever I did with caddy got around that?

Point taken. Looking through it again with that in mind, I think this is what nginx does:

  1. Receive request for nextcloud.home:80/
  2. Forward request to 127.0.0.1:8080/index.php
  • Since it was asked to rewrite //index.php and then forward it to 127.0.0.1:8080
  1. Receive request from itself again on 127.0.0.1:8080/index.php
  2. Pass request to php-fpm via the fastcgi protocol
  • Note that nextcloud will not detect domain tampering because nginx forwards the host name via the Host request header

You could skip half of this if you simply removed the nginx virtualhost entirely, and let the nextcloud module do its job.

I have even less of a chance of figuring out what you did with your ports in the past, but it’s pretty clear to me that nginx is the only service running on port 80 right now. For reference in the future, you can use lsof -i :<portnumber> to find what process is listening on a given port.

Or even lsof -i to see all ports in use, this might help convince you that the nextcloud.home virtual host is useless :wink:

One of the benefits of using virtual hosts to provide subdomains is that you only need to open port 80 on the firewall, by the way. nginx will forward requests to 8096 locally, without passing through the firewall.

Ok, it’s finally sinking in. I thought if I didn’t have the explicit virtualhost for nextcloud.home, anything.home (that wasn’t elsewhere in the list of virtualhosts) would default to my nextcloud, which I was trying to avoid. But that’s not the case because I passed the hostname to the nextcloud module which figures out the domain name for me. This is all making a lot more sense.