Blog ⋅ Peter Nelson

Using Git to auto-publish a website to Windows Server 2008: Remastered

by peterdn 15. July 2012 17:22

My previous blog post on this subject is now out of date, mainly because the version of COPSSH that I used is no longer a free product. What follows is an updated guide, using bog-standard Cygwin to achieve the same results.

Note that this guide is largely given “by example”. The reader will very likely have to make changes to the names of paths and URLs to fit their own particular setup. It is also assumed that the reader is already familiar with Cygwin (or at least Unix) and of course, Windows Server 2008.

Setting up OpenSSH

  1. Download and install Cygwin if you have not already done so. For our purposes, the only additional packages required are OpenSSH and Git. A text editor such as vim or mcedit is also useful. I installed mine to C:\cygwin.

  2. Launch the Cygwin terminal (as administrator). Run the ssh-host-config command to set up our SSH server. The following interactive dialog is then initiated. Be sure to answer ‘yes’ to use privilege separation, to create an sshd user, to install sshd as a service, and to create a privileged user cyg_server:

    $ ssh-host-config
    *** Info: Generating /etc/ssh_host_key
    *** Info: Generating /etc/ssh_host_rsa_key
    *** Info: Generating /etc/ssh_host_dsa_key
    *** Info: Generating /etc/ssh_host_ecdsa_key
    *** Info: Creating default /etc/ssh_config file
    *** Info: Creating default /etc/sshd_config file
    *** Info: Privilege separation is set to yes by default since OpenSSH 3.3.
    *** Info: However, this requires a non-privileged account called 'sshd'.
    *** Info: For more info on privilege separation read /usr/share/doc/openssh/README.privsep.
    *** Query: Should privilege separation be used? (yes/no) yes
    *** Info: Note that creating a new user requires that the current account have
    *** Info: Administrator privileges.  Should this script attempt to create a
    *** Query: new local account 'sshd'? (yes/no) yes
    *** Info: Updating /etc/sshd_config file
    *** Query: Do you want to install sshd as a service?
    *** Query: (Say "no" if it is already installed as a service) (yes/no) yes
    *** Query: Enter the value of CYGWIN for the daemon: []
    *** Info: On Windows Server 2003, Windows Vista, and above, the
    *** Info: SYSTEM account cannot setuid to other users -- a capability
    *** Info: sshd requires.  You need to have or to create a privileged
    *** Info: account.  This script will help you do so.
    *** Info: You appear to be running Windows XP 64bit, Windows 2003 Server,
    *** Info: or later.  On these systems, it's not possible to use the LocalSystem
    *** Info: account for services that can change the user id without an
    *** Info: explicit password (such as passwordless logins [e.g. public key
    *** Info: authentication] via sshd).
    *** Info: If you want to enable that functionality, it's required to create
    *** Info: a new account with special privileges (unless a similar account
    *** Info: already exists). This account is then used to run these special
    *** Info: servers.
    *** Info: Note that creating a new user requires that the current account
    *** Info: have Administrator privileges itself.
    *** Info: No privileged account could be found.
    *** Info: This script plans to use 'cyg_server'.
    *** Info: 'cyg_server' will only be used by registered services.
    *** Query: Do you want to use a different name? (yes/no) no
    *** Query: Create new privileged user account 'cyg_server'? (yes/no) yes
    *** Info: Please enter a password for new user cyg_server.  Please be sure
    *** Info: that this password matches the password rules given on your system.
    *** Info: Entering no password will exit the configuration.
    *** Query: Please enter the password: XXXXX
    *** Query: Reenter: XXXXX
    *** Info: User 'cyg_server' has been created with password 'XXXXX'.
    *** Info: If you change the password, please remember also to change the
    *** Info: password for the installed services which use (or will soon use)
    *** Info: the 'cyg_server' account.
    *** Info: Also keep in mind that the user 'cyg_server' needs read permissions
    *** Info: on all users' relevant files for the services running as 'cyg_server'.
    *** Info: In particular, for the sshd server all users' .ssh/authorized_keys
    *** Info: files must have appropriate permissions to allow public key
    *** Info: authentication. (Re-)running ssh-user-config for each user will set
    *** Info: these permissions correctly. [Similar restrictions apply, for
    *** Info: instance, for .rhosts files if the rshd server is running, etc].
    *** Info: The sshd service has been installed under the 'cyg_server'
    *** Info: account.  To start the service now, call `net start sshd' or
    *** Info: `cygrunsrv -S sshd'.  Otherwise, it will start automatically
    *** Info: after the next reboot.
    *** Info: Host configuration finished. Have fun!
  3. (Optional) To disable root and password logins, add the following line to /etc/sshd_config:

    PermitRootLogin no
    PasswordAuthentication no
  4. (Optional) To allow only a specific user (or users) to connect, add the following line to /etc/sshd_config:

    AllowUsers <username>
  5. (Optional) To synchronize Windows user accounts with Cygwin, for example if a new Windows user was created:

    mkpasswd -l > /etc/passwd
  6. Start the service with net start sshd.

Enabling public key authentication

  1. It is advisable to use a standard Windows user account for SSH and Git access. For the rest of the guide I will assume we are using a user called ‘newuser’.

  2. You may need to create a home and .ssh directory for newuser if they do not already exist:

    mkdir -p /home/newuser/.ssh
    chown -R newuser /home/newuser
  3. Add your public key info to /home/newuser/.ssh/authorized_keys. If you need to create these and don’t know how, there are plenty of guides for using either PuTTYgen to achieve this.

  4. Test that you can now log in via SSH. If not, make sure that the sshd service is running, your firewall is properly configured, and your user settings and keys are all correct.

Setting up Git

  1. Log in to the remote server via SSH. If you can do this, most of the work is already done.

  2. Create a new empty Git repository on the remote server. For example:

    $ git init --bare ~/test.git
    Initialized empty Git repository in /home/newuser/test.git/
  3. If everything is set up correctly, cloning the remote repository should now work. For example, with my setup:

    $ git clone ssh://
    Cloning into 'test'...
    Enter passphrase for key '/home/Peter/.ssh/id_rsa':
    warning: You appear to have cloned an empty repository.
  4. Make a few additions and test pushing:

    $ cd test/
    $ echo hello > hello.txt
    $ git add hello.txt
    $ git commit -m "Test commit"
    [master (root-commit) f465617] Test commit
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 hello.txt
    $ git push origin master
    Enter passphrase for key '/home/Peter/.ssh/id_rsa':
    Counting objects: 3, done.
    Writing objects: 100% (3/3), 223 bytes, done.
    Total 3 (delta 0), reused 0 (delta 0)
    To ssh://
     * [new branch]      master –> master


Auto-publishing a website

The idea is that we maintain a central bare Git repository and our website is a clone of this. We use the Git post-receive hook to automatically pull changes from the central repository to the website. Therefore, when we push changes from a remote client, these changes are automatically reflected in the website. In my setup, my bare Git repository is located in C:\inetpub\git\mysite.git and my wwwroot is located in C:\inetpub\wwwroot\

  1. Log in to the remote server via SSH. Create symbolic links to the git and wwwroot directories for convenience (remember we are in a cygwin environment here, and also make sure newuser has the required permissions on these directories):

    ln -s /cygdrive/c/inetpub/git git
    ln -s /cygdrive/c/inetpub/wwwroot wwwroot

    The bare repository can now be accessed at ssh://

  2. Ensure that your website clone has a remote that correctly points to the bare repository (cygwin path; the local remote is correct in this case):

    $ cd ~/wwwroot/
    $ git remote -v
    local   /home/newuser/git/mysite.git (fetch)
    local   /home/newuser/git/mysite.git (push)
    origin  C:/inetpub/git/test.git (fetch)
    origin  C:/inetpub/git/test.git (push)

    If not, add a new one. For example:

    $ git remote add local /home/newuser/git/mysite.git
  3. Add the following to the /home/newuser/git/mysite.git/hooks/post-receive script. This executes when a push is completed successfully. It is necessary to unset GIT_DIR so that git-pull uses the current working directory instead of the target git repository:

    unset GIT_DIR
    cd /home/newuser/wwwroot/
    git pull local master
  4. Done!

Tags: , , ,


Using Git to auto-publish a website to Windows Server 2008

by peterdn 4. November 2011 10:07

(Updated July 15 2012.  much of the information presented here is now obsolete -- check out my updated guide.)

This is a quick and dirty guide.  YMMV.

I was motivated by the following:

  1. I want to work on and publish my website from several different machines, running different operating systems, with a minimum of fuss.
  2. After saving any changes to my central repository, I ideally want to see those changes with only a refresh of my browser.
  3. FTP publishing is generally clunky as hell.
  4. I’d done a similar thing before for a client, using SVN, and it worked well.
  5. Git is great.
  6. Whilst GitHub is fantastic, it costs money for private repositories and I would rather not pay for something when I can use my own VPS.
  7. Auto-publishing to my web server would be more difficult if my code was hosted on GitHub, anyway.

Setting up a Git server on Windows

This part is heavily inspired by the excellent and detailed guide by Tim Davis.  Since writing, some of his steps have changed and/or are unnecessary and/or I chose to do them in a slightly different way.  If you run into any problems with any of the steps listed here, please check out his guide for some solutions to common issues.  Here follows a brief summary of what I did:

  1. Download and install COPSSH to C:\ssh.

  2. Create a new Windows user (optional; you can enable SSH access for an existing user if you prefer).

  3. Open COPSSH Control Panel –> Users –> Add.  Enter details for the user account <user> you want to enable SSH access for.  Choose Linux shell + Sftp.  I chose to allow both password and public key authentication.

  4. Add a new inbound rule to the Windows Firewall to allow SSH traffic in (port 22, unless you changed it).

  5. Add your public key to C:\ssh\home\<user>\.ssh\authorized_keys (create if needed).  If you need to generate a public/private key pair, use PuTTYgen.

  6. Test that everything is working so far by attempting to connect from another machine.

  7. Download and install msysgit (at time of writing, Git- is the file you want) to C:\git.

  8. Open C:\ssh\home\<user>\.bashrc and add the following to the end of the file:

    export PATH=$PATH:/cygdrive/c/git/bin
  9. Copy git-receive-pack.exe and git-upload-pack.exe from C:\git\libexec\git-core to C:\git\bin.

  10. Verify that everything works by initialising a bare repository, cloning it, and pushing some changes.  If you have a repository in C:\ssh\home\<user>\test.git, it can be accessed via ssh://<user>@<hostname>/ssh/home/<user>/test.git (TODO: find out why it’s rooted at /ssh).  The following command-line session is fairly representative of success:

    peterdn@ubuntu:~$ ssh
    Last login: Thu Nov  3 21:45:58 2011 from
    peterdn@WIN-53SSN6PLF6F ~
    $ mkdir test.git
    peterdn@WIN-53SSN6PLF6F ~
    $ git init --bare test.git/
    Initialized empty Git repository in C:/ssh/home/peterdn/test.git/
    peterdn@WIN-53SSN6PLF6F ~
    $ exit
    Connection to closed.
    peterdn@ubuntu:~$ git clone ssh://peterdn@ test
    Initialized empty Git repository in /home/peterdn/test/.git/
    warning: You appear to have cloned an empty repository.
    peterdn@ubuntu:~$ cd test
    peterdn@ubuntu:~/test$ echo hello > hello.txt
    peterdn@ubuntu:~/test$ git add hello.txt
    peterdn@ubuntu:~/test$ git commit -m "initial commit"
    [master (root-commit) 64f843d] initial commit
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 hello.txt
    peterdn@ubuntu:~/test$ git push origin master
    Counting objects: 3, done.
    Writing objects: 100% (3/3), 219 bytes, done.
    Total 3 (delta 0), reused 0 (delta 0)
    To ssh://peterdn@
     * [new branch]      master –> master

Setting up a repository for auto-publish

Now that we have our Git server up and running, we can begin to invoke the black magic required to implement auto-publishing.  This part of the guide will be purely by-example; your setup with undoubtedly differ from mine, but hopefully the principles remain the same.  My desired setup looks like so:

  • Git repository is located in C:\inetpub\git\mysite.git.
  • Website is located in C:\inetpub\wwwroot\  This is a clone of the above repository.

Note: for some reason that I’m yet to fathom, my git*.exe binaries live in a different virtual directory environment to the shell, and of course, Windows.  For example, what my shell thinks is /home/peterdn, git.exe thinks is /c/ssh/home/peterdn.  This has turned out to bite me a couple of times, but if you’re aware of the problem, it might make things easier to diagnose.


  1. In C:\ssh\home\<user>, create a symbolic link to C:\inetpub\git, using the following command:

    mklink.exe /D git C:\inetpub\git
  2. Similarly, create a symbolic link to C:\inetpub\wwwroot.

  3. Make sure that <user> has appropriate permissions for C:\inetpub\git\mysite.git and C:\inetpub\wwwroot\

  4. Due to the bizarre path problem mentioned above, and the fact that I’d cloned <website> from within cmd.exe, my remote config currently looks like:

    $ git remote -v
    origin  C:\inetpub\git\mysite.git (fetch)
    origin  C:\inetpub\git\mysite.git (push)

    This will make things unhappy if we attempt to pull from origin in the Cygwin environment.  Therefore, I added another remote:

    git remote add local /c/ssh/home/peterdn/git/mysite.git
  5. Now we add the hook that will automatically pull changes whenever the main repository receives changes.  Add the following to C:\inetpub\git\mysite.git\hooks\post-receive:

    unset GIT_DIR
    cd /c/ssh/home/peterdn/wwwroot/
    git pull local master
  6. Verify that it works by pushing some changes.

Future Improvements

The results of the above are sufficient for the moment.  However, there are a few things that I would consider changing or adding in the future:

  1. Remove the requirement for the /ssh/home/<user>/git component from the URL, as it looks messy and is just generally bad form.
  2. Sort out issues with different virtual directory structures.
  3. Have a separate website for testing out changes without having to mess with the live site.  I assume this is simple as having separate “stable” and “current” branches, and pulling from these to the appropriate places.  But I could be wrong.
  4. Remove the .git directory from my web root.  I believe this is possible using a detached work tree.
  5. GitHub-style web interface for managing my repositories.

Tags: , , ,