To use private modules when deploying a Hugo site from GitHub to Netlify, you’ll probably run into trouble to get the Netlify to fetch the modules. This note describes a workaround.


  • The authorization you give to Netlify to fetch private GitHub repositories don’t transfer to the build process, which is when Hugo tries to fetch modules.
  • The build command can be configured to access GitHub through a deploy key, but only works for one module because GitHub doesn’t let you assign the same deploy key to more than one repository.

The usual workaround suggested for the latter is a “machine user” — an ad-hoc GitHub account with access to all involved repositories — but that’s overhead. While the strategy below isn’t free of overhead, it lives closer to where it works, so is less likely to turn into cruft.


  • Generate one deploy key for every private module repository
  • Provide Netlify with deploy keys for all private modules using environment variables
  • Fetch modules with git instead of Hugo
  • Use Hugo’s module replacements feature to point Hugo to the now-local modules


Assuming we want private modules and

Generate a key pair for every module:

ssh-keygen -q -N -f mod1.key

Reformat the private key, replacing newlines with underscores, so it can go on one line:

tr '\n' '_' < mod1.key

Copy the result to Netlify as an environment variable named SSH_KEY_mod1.

Repeat for mod2.

Add or modify netlify.toml to your site repository:

  command = './'

# Ensure that you use versions that support Hugo modules. At the time of
# writing, the default Hugo version on Netlify doesn't.
  GO_VERSION = '1.15'
  HUGO_VERSION = '0.79.0'

Add a file to your site repository and make it executable:

set -e

# Copy the key for mod1 from the environment to the filesystem
echo -e "${SSH_KEY_mod1//_/\\n}" > /tmp/mod1.key
chmod 600 /tmp/mod1.key

# Clone mod1
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i /tmp/mod1.key"
git clone /tmp/mod1

# Clone mod2
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i /tmp/mod2.key"
git clone /tmp/mod2

# Tell Hugo to use the locally downloaded mod1 and mod2
export HUGO_MODULE_REPLACEMENTS=">/tmp/mod1,>/tmp/mod2"

# Run Hugo


This strategy uses the latest version of every module and the script needs to be updated whenever modules are added or removed. It should be possible to use go.mod exclusively as the script’s input by overengineering extending somewhat.