SparkLabs Forum.

Community Help.

Use .opvn file extension


If I export a VPN-Config the file extension is .conf... Why it isn't standard .ovpn extension?

I only discovered it, because I tried the OpenVPN-App for iOS, which needs config files with the .ovpn extension..

Hi Michael,

Viscosity predates popular use of the .ovpn extension (back when Viscosity was first developed .conf was the way to go). Viscosity's configurations also must store additional Viscosity configurations, bundle in additional scripts, etc., so it would be misleading to indicate that they are simply standard raw OpenVPN config files. We are looking at making an export option designed for the iOS client, however in the meantime here is our recommended method:

1. Select your connection in Viscosity's Preferences window
2. Click on the small cog button near the bottom right and select "Export Connection". Save it to your Desktop.
3. If you are using a Mac, right-click on your saved connection in the Finder and select "Show Package Contents". Under Windows you can simply double-click on the folder.
4. Rename the "config.conf" file to end with ".ovpn" instead (you can give it a different name as well if you wish).
5. Drag all of the files into the OpenVPN Documents section in iTunes (the iOS app doesn't require them all to be a single file).

So I had this problem this evening, and I just went ahead and created a gist for it using Python... ... 1071be5946

Basic steps are:

1. Install click: pip install click
2. Usage: <input> <output>


a. Export .visc or .visz to desktop
b. python ~/Desktop/<filename>.vis{c,v}

LICENSE: Public Domain

Code (

Code: Select all

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Converts viscosity export files into an open vpn package
Usage: <input> <output>
import io
import os
import sys
import tarfile

import click

if sys.version.startswith('3'):
    unicode = str

# ----------------------------------------------------------------------
# Exceptions
# ----------------------------------------------------------------------
class ConversionError(Exception):
    """Base conversion error"""

class NoConnectionName(ConversionError):
    """No connection name was available"""

class NoCertificateData(ConversionError):
    """No certificate data was found within certificate file"""

class NoCertificateFile(ConversionError):
    """File was not available within archive"""

# ----------------------------------------------------------------------
# Command-line Interface
# ----------------------------------------------------------------------
@click.argument('input-path', type=click.Path(exists=True))
@click.argument('output', required=False, type=click.Path(), default=None)
def convert(input_path, output=None):
    '''Converts Viscosity package

        input (str): path to folder or file input
        output (str): path to folder output  [default: None]
    if input_path.endswith('.visc'):
        output = input_path if output is None else output
        if output and not os.path.exists(output):
            output = input_path
        files = [os.path.join(input_path, filename) for filename in os.listdir(input_path)]
        for config_fp in files:
            new_config = []
            if config_fp.endswith('.conf'):
                with, encoding='utf-8') as stream:
                    connection_name = extract(stream, new_config, input_path=input_path)

                new_config.insert(0, '# OpenVPN Config for {}'.format(connection_name))
                new_config = '\n'.join(new_config) + '\n'
                output_filepath = os.path.join(output, '{}.ovpn'.format(connection_name))
                with, 'w', encoding='utf-8') as out:

                print('Wrote: {}'.format(output_filepath))

    elif input_path.endswith('.visz'):
        if output is None:
            output = os.path.dirname(input_path)

        data = {}
        with as zipped:
            for filepath, fileinfo in zip(zipped.getnames(), zipped.getmembers()):
                if not fileinfo.isfile():
                filename = filepath.split(os.path.sep)[-1]
                data[filename] = zipped.extractfile(filepath).read()

        for key in data:
            if not key.endswith('.conf') or key.startswith('.'):

            import pdb; pdb.set_trace()
            new_config = []
            lines = data[key].split('\n')
            connection_name = extract(lines, new_config, file_data=data)

            new_config.insert(0, '# OpenVPN Config for {}'.format(connection_name))
            new_config = '\n'.join(new_config) + '\n'
            output_filepath = os.path.join(output, '{}.ovpn'.format(connection_name))
            with, 'w', encoding='utf-8') as out:

            print('Wrote: {}'.format(output_filepath))

# ----------------------------------------------------------------------
# CLI Support
# ----------------------------------------------------------------------
def extract(data, new_config, input_path=None, file_data={}):
    certificate_files = ['ca', 'cert', 'key', 'tls-auth']
    connection_name = ''
    for line in data:
        line = line.rstrip()

        if not line.strip():

        # This was an invalid configuration, for some reason
        elif line == 'compress lzo':

        elif line.startswith('#'):
            if line.startswith('#viscosity name'):
                connection_name = line.split('#viscosity name ', 1)[-1]
                connection_name = connection_name.strip()

            key, value = line.split(' ', 1)
            value = value.strip()
        except ValueError:
            key, value = line, ''

        if key in certificate_files:
            if key == 'tls-auth':
                value, direction = value.split(' ', 1)
                if direction:
                    new_config.append('key-direction {}'.format(direction))

            if input_path:
                cert_filepath = os.path.join(input_path, value)
                with, encoding='utf-8') as cf:
                    certificate =
                if value not in file_data:
                    raise NoCertificateFile('Could not find certificate file in archive')
                certificate = file_data.get(value)

            if not certificate:
                raise NoCertificateData('Could not find certificate data')

            new_config.append('<%s>' % key)
            new_config.append('</%s>' % key)


    if not connection_name.strip():
        raise NoConnectionName('Could not find connection name in file.  Aborting')

    return connection_name

if __name__ == '__main__':
3 posts Page 1 of 1

Copyright © 2016 SparkLabs Pty Ltd. All Rights Reserved. Privacy Policy